quo_vadis 2.1.11 → 2.2.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/CHANGELOG.md +16 -0
- data/README.md +85 -100
- data/app/controllers/quo_vadis/confirmations_controller.rb +26 -61
- data/app/controllers/quo_vadis/password_resets_controller.rb +64 -32
- data/app/controllers/quo_vadis/sessions_controller.rb +0 -5
- data/app/mailers/quo_vadis/mailer.rb +2 -2
- data/app/models/quo_vadis/account.rb +32 -0
- data/app/models/quo_vadis/password.rb +6 -1
- data/app/views/quo_vadis/confirmations/new.html.erb +19 -7
- data/app/views/quo_vadis/mailer/account_confirmation.text.erb +2 -3
- data/app/views/quo_vadis/mailer/reset_password.text.erb +2 -2
- data/app/views/quo_vadis/password_resets/edit.html.erb +13 -1
- data/app/views/quo_vadis/password_resets/new.html.erb +2 -2
- data/app/views/quo_vadis/sessions/new.html.erb +1 -1
- data/config/locales/quo_vadis.en.yml +9 -7
- data/config/routes.rb +4 -12
- data/lib/quo_vadis/controller.rb +21 -11
- data/lib/quo_vadis/defaults.rb +2 -2
- data/lib/quo_vadis/version.rb +1 -1
- data/lib/quo_vadis.rb +2 -2
- data/test/README.md +6 -0
- data/test/dummy/app/controllers/articles_controller.rb +1 -0
- data/test/dummy/app/controllers/sign_ups_controller.rb +2 -11
- data/test/fixtures/quo_vadis/mailer/account_confirmation.text +2 -3
- data/test/fixtures/quo_vadis/mailer/reset_password.text +2 -2
- data/test/integration/account_confirmation_test.rb +42 -86
- data/test/integration/controller_test.rb +8 -8
- data/test/integration/logging_test.rb +31 -7
- data/test/integration/password_login_test.rb +1 -1
- data/test/integration/password_reset_test.rb +89 -54
- data/test/integration/sessions_test.rb +16 -0
- data/test/mailers/mailer_test.rb +2 -2
- data/test/models/account_test.rb +48 -0
- data/test/models/session_test.rb +4 -0
- metadata +4 -11
- data/app/models/quo_vadis/account_confirmation_token.rb +0 -17
- data/app/models/quo_vadis/password_reset_token.rb +0 -17
- data/app/models/quo_vadis/token.rb +0 -42
- data/app/views/quo_vadis/confirmations/edit.html.erb +0 -10
- data/app/views/quo_vadis/confirmations/edit_email.html.erb +0 -14
- data/app/views/quo_vadis/confirmations/index.html.erb +0 -14
- data/app/views/quo_vadis/password_resets/index.html.erb +0 -5
- data/test/models/token_test.rb +0 -70
|
@@ -163,22 +163,42 @@ class LoggingTest < IntegrationTest
|
|
|
163
163
|
|
|
164
164
|
|
|
165
165
|
test 'password.reset' do
|
|
166
|
+
assert_emails 1 do
|
|
167
|
+
post quo_vadis.password_reset_path(email: 'bob@example.com')
|
|
168
|
+
follow_redirect!
|
|
169
|
+
end
|
|
170
|
+
|
|
166
171
|
assert_difference 'QuoVadis::Log.count', 2 do
|
|
167
|
-
|
|
168
|
-
|
|
172
|
+
put quo_vadis.password_reset_path(password: {
|
|
173
|
+
otp: extract_code_from_email,
|
|
174
|
+
password: 'secretsecret',
|
|
175
|
+
password_confirmation: 'secretsecret',
|
|
176
|
+
})
|
|
169
177
|
end
|
|
178
|
+
|
|
170
179
|
assert_equal QuoVadis::Log::PASSWORD_RESET, QuoVadis::Log.first.action
|
|
171
180
|
assert_equal QuoVadis::Log::LOGIN_SUCCESS, log.action
|
|
172
181
|
end
|
|
173
182
|
|
|
174
183
|
|
|
175
184
|
test 'account.confirmation' do
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
185
|
+
QuoVadis.accounts_require_confirmation true
|
|
186
|
+
|
|
187
|
+
assert_emails 1 do
|
|
188
|
+
login
|
|
189
|
+
assert_redirected_to '/articles/secret'
|
|
190
|
+
follow_redirect!
|
|
191
|
+
assert_redirected_to '/confirm'
|
|
192
|
+
follow_redirect!
|
|
179
193
|
end
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
|
|
195
|
+
assert_difference 'QuoVadis::Log.count' do
|
|
196
|
+
post quo_vadis.confirm_path(otp: extract_code_from_email)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
assert_equal QuoVadis::Log::ACCOUNT_CONFIRMATION, log.action
|
|
200
|
+
ensure
|
|
201
|
+
QuoVadis.accounts_require_confirmation false
|
|
182
202
|
end
|
|
183
203
|
|
|
184
204
|
|
|
@@ -241,4 +261,8 @@ class LoggingTest < IntegrationTest
|
|
|
241
261
|
end
|
|
242
262
|
end
|
|
243
263
|
|
|
264
|
+
def extract_code_from_email
|
|
265
|
+
ActionMailer::Base.deliveries.last.decoded[%r{\d{6}}, 0]
|
|
266
|
+
end
|
|
267
|
+
|
|
244
268
|
end
|
|
@@ -14,102 +14,144 @@ class PasswordResetTest < IntegrationTest
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
test 'unknown identifier' do
|
|
17
|
-
post quo_vadis.
|
|
18
|
-
assert_redirected_to quo_vadis.
|
|
19
|
-
assert_equal '
|
|
17
|
+
post quo_vadis.password_reset_path(email: 'foo@example.com')
|
|
18
|
+
assert_redirected_to quo_vadis.edit_password_reset_path
|
|
19
|
+
assert_equal 'Please check your email for your reset code.', flash[:notice]
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
test 'known identifier' do
|
|
24
24
|
assert_emails 1 do
|
|
25
|
-
post quo_vadis.
|
|
25
|
+
post quo_vadis.password_reset_path(email: 'bob@example.com')
|
|
26
26
|
end
|
|
27
|
-
assert_redirected_to quo_vadis.
|
|
28
|
-
assert_equal '
|
|
27
|
+
assert_redirected_to quo_vadis.edit_password_reset_path
|
|
28
|
+
assert_equal 'Please check your email for your reset code.', flash[:notice]
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
test '
|
|
32
|
+
test 'reset code expired' do
|
|
33
33
|
assert_emails 1 do
|
|
34
|
-
post quo_vadis.
|
|
34
|
+
post quo_vadis.password_reset_path(email: 'bob@example.com')
|
|
35
|
+
follow_redirect!
|
|
35
36
|
end
|
|
36
|
-
get extract_url_from_email
|
|
37
|
-
assert_response :success
|
|
38
|
-
end
|
|
39
37
|
|
|
38
|
+
travel QuoVadis.password_reset_otp_lifetime + 1.minute
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
# type in reset code from email
|
|
41
|
+
code = extract_code_from_email
|
|
42
|
+
put quo_vadis.password_reset_path(password: {
|
|
43
|
+
otp: code,
|
|
44
|
+
password: 'secretsecret',
|
|
45
|
+
password_confirmation: 'secretsecret',
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
assert_equal 'Your reset code has expired. Please request another one.', flash[:alert]
|
|
47
49
|
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
50
|
end
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
test '
|
|
53
|
+
test 'reset code incorrect' do
|
|
53
54
|
assert_emails 1 do
|
|
54
|
-
post quo_vadis.
|
|
55
|
+
post quo_vadis.password_reset_path(email: 'bob@example.com')
|
|
56
|
+
follow_redirect!
|
|
55
57
|
end
|
|
56
|
-
put quo_vadis.password_reset_path(extract_token_from_email, password: {password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx'})
|
|
57
|
-
assert controller.logged_in?
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
# type in reset code from email
|
|
60
|
+
put quo_vadis.password_reset_path(password: {
|
|
61
|
+
otp: '000000',
|
|
62
|
+
password: 'secretsecret',
|
|
63
|
+
password_confirmation: 'secretsecret',
|
|
64
|
+
})
|
|
65
|
+
|
|
60
66
|
assert_redirected_to quo_vadis.new_password_reset_path
|
|
61
|
-
assert_equal '
|
|
67
|
+
assert_equal 'Sorry, the code was incorrect. Please try again.', flash[:alert]
|
|
62
68
|
end
|
|
63
69
|
|
|
64
70
|
|
|
65
71
|
test 'new password invalid' do
|
|
66
|
-
digest = @user.qv_account.password.password_digest
|
|
67
|
-
|
|
68
72
|
assert_emails 1 do
|
|
69
|
-
post quo_vadis.
|
|
73
|
+
post quo_vadis.password_reset_path(email: 'bob@example.com')
|
|
74
|
+
follow_redirect!
|
|
70
75
|
end
|
|
71
76
|
|
|
77
|
+
# type in reset code from email
|
|
78
|
+
code = extract_code_from_email
|
|
72
79
|
assert_no_difference 'QuoVadis::Session.count' do
|
|
73
|
-
put quo_vadis.password_reset_path(
|
|
80
|
+
put quo_vadis.password_reset_path(password: {
|
|
81
|
+
otp: code,
|
|
82
|
+
password: 'secret',
|
|
83
|
+
password_confirmation: 'secret',
|
|
84
|
+
})
|
|
74
85
|
end
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
assert_response 422
|
|
88
|
+
assert_nil flash[:notice]
|
|
89
|
+
refute controller.logged_in?
|
|
79
90
|
end
|
|
80
91
|
|
|
81
92
|
|
|
82
93
|
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
94
|
phone = session_login
|
|
95
|
+
tablet = session_login
|
|
89
96
|
|
|
90
97
|
get articles_url
|
|
91
98
|
refute controller.logged_in?
|
|
92
99
|
|
|
93
100
|
assert_emails 1 do
|
|
94
|
-
post quo_vadis.
|
|
101
|
+
post quo_vadis.password_reset_path(email: 'bob@example.com')
|
|
102
|
+
follow_redirect!
|
|
95
103
|
end
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
# type in reset code from email
|
|
106
|
+
code = extract_code_from_email
|
|
107
|
+
# deletes phone and tablet sessions and creates new session
|
|
108
|
+
assert_difference 'QuoVadis::Session.count', -1 do
|
|
109
|
+
put quo_vadis.password_reset_path(password: {
|
|
110
|
+
otp: code,
|
|
111
|
+
password: 'secretsecret',
|
|
112
|
+
password_confirmation: 'secretsecret',
|
|
113
|
+
})
|
|
99
114
|
end
|
|
100
115
|
|
|
116
|
+
assert_equal 'Your password has been changed and you are logged in.', flash[:notice]
|
|
101
117
|
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.
|
|
118
|
+
assert_redirected_to '/articles/secret'
|
|
105
119
|
|
|
106
120
|
phone.get articles_url
|
|
107
121
|
refute phone.controller.logged_in?
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
tablet.get articles_url
|
|
124
|
+
refute tablet.controller.logged_in?
|
|
125
|
+
end
|
|
110
126
|
|
|
111
|
-
|
|
112
|
-
|
|
127
|
+
|
|
128
|
+
test 'reset code cannot be reused' do
|
|
129
|
+
assert_emails 1 do
|
|
130
|
+
post quo_vadis.password_reset_path(email: 'bob@example.com')
|
|
131
|
+
follow_redirect!
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# type in reset code from email
|
|
135
|
+
code = extract_code_from_email
|
|
136
|
+
put quo_vadis.password_reset_path(password: {
|
|
137
|
+
otp: code,
|
|
138
|
+
password: 'secretsecret',
|
|
139
|
+
password_confirmation: 'secretsecret',
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
# The password-reset process is now finished.
|
|
143
|
+
# Test what happens if reset code is resubmitted with a different password.
|
|
144
|
+
|
|
145
|
+
put quo_vadis.password_reset_path(password: {
|
|
146
|
+
otp: code,
|
|
147
|
+
password: 'foobarfoobar',
|
|
148
|
+
password_confirmation: 'foobarfoobar',
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
assert_redirected_to quo_vadis.new_password_reset_path
|
|
152
|
+
assert_nil flash[:alert]
|
|
153
|
+
refute @user.qv_account.password.authenticate('foobarfoobar')
|
|
154
|
+
assert @user.qv_account.password.authenticate('123456789abc')
|
|
113
155
|
end
|
|
114
156
|
|
|
115
157
|
|
|
@@ -121,16 +163,9 @@ class PasswordResetTest < IntegrationTest
|
|
|
121
163
|
end
|
|
122
164
|
end
|
|
123
165
|
|
|
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
166
|
|
|
132
|
-
def
|
|
133
|
-
|
|
167
|
+
def extract_code_from_email
|
|
168
|
+
ActionMailer::Base.deliveries.last.decoded[%r{\d{6}}, 0]
|
|
134
169
|
end
|
|
135
170
|
|
|
136
171
|
end
|
|
@@ -69,6 +69,22 @@ class SessionsTest < IntegrationTest
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
test 'non-authentication session data is not removed on logout' do
|
|
73
|
+
desktop = login
|
|
74
|
+
session_id = desktop.session.id
|
|
75
|
+
|
|
76
|
+
desktop.get secret_articles_path
|
|
77
|
+
assert_equal 'bar', desktop.session[:foo]
|
|
78
|
+
|
|
79
|
+
desktop.delete quo_vadis.logout_path
|
|
80
|
+
refute desktop.controller.logged_in?
|
|
81
|
+
|
|
82
|
+
desktop.get articles_path
|
|
83
|
+
assert_equal 'bar', desktop.session[:foo]
|
|
84
|
+
refute_equal session_id, desktop.session.id
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
72
88
|
private
|
|
73
89
|
|
|
74
90
|
# starts a new rails session and logs in
|
data/test/mailers/mailer_test.rb
CHANGED
|
@@ -12,7 +12,7 @@ class MailerTest < ActionMailer::TestCase
|
|
|
12
12
|
test 'reset_password' do
|
|
13
13
|
email = QuoVadis::Mailer.with(
|
|
14
14
|
email: 'Foo <foo@example.com>',
|
|
15
|
-
|
|
15
|
+
otp: '314159'
|
|
16
16
|
).reset_password
|
|
17
17
|
|
|
18
18
|
assert_emails 1 do
|
|
@@ -29,7 +29,7 @@ class MailerTest < ActionMailer::TestCase
|
|
|
29
29
|
test 'account_confirmation' do
|
|
30
30
|
email = QuoVadis::Mailer.with(
|
|
31
31
|
email: 'Foo <foo@example.com>',
|
|
32
|
-
|
|
32
|
+
otp: 271828
|
|
33
33
|
).account_confirmation
|
|
34
34
|
|
|
35
35
|
assert_emails 1 do
|
data/test/models/account_test.rb
CHANGED
|
@@ -53,4 +53,52 @@ class AccountTest < ActiveSupport::TestCase
|
|
|
53
53
|
assert_empty account.sessions
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
|
|
57
|
+
test 'otp_for_confirmation' do
|
|
58
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
59
|
+
account = u.qv_account
|
|
60
|
+
|
|
61
|
+
otp = account.otp_for_confirmation(1)
|
|
62
|
+
assert_match /^\d{6}$/, otp
|
|
63
|
+
refute_equal otp, account.otp_for_confirmation(2)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
test 'confirm' do
|
|
68
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
69
|
+
account = u.qv_account
|
|
70
|
+
|
|
71
|
+
otp = account.otp_for_confirmation(1)
|
|
72
|
+
refute account.confirm('000000', 1)
|
|
73
|
+
refute account.confirm(otp, 2)
|
|
74
|
+
|
|
75
|
+
assert account.confirm(otp, 1)
|
|
76
|
+
assert account.confirmed?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
test 'otp_for_password_reset' do
|
|
81
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
82
|
+
account = u.qv_account
|
|
83
|
+
|
|
84
|
+
otp = account.otp_for_password_reset(1)
|
|
85
|
+
assert_match /^\d{6}$/, otp
|
|
86
|
+
refute_equal otp, account.otp_for_password_reset(2)
|
|
87
|
+
|
|
88
|
+
account.password.change('123456789abc', 'secretsecret', 'secretsecret')
|
|
89
|
+
refute_equal otp, account.otp_for_password_reset(1)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
test 'verify_password_reset' do
|
|
94
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
95
|
+
account = u.qv_account
|
|
96
|
+
|
|
97
|
+
otp = account.otp_for_password_reset(1)
|
|
98
|
+
refute account.verify_password_reset('000000', 1)
|
|
99
|
+
refute account.verify_password_reset(otp, 2)
|
|
100
|
+
|
|
101
|
+
assert account.verify_password_reset(otp, 1)
|
|
102
|
+
end
|
|
103
|
+
|
|
56
104
|
end
|
data/test/models/session_test.rb
CHANGED
|
@@ -2,6 +2,10 @@ require 'test_helper'
|
|
|
2
2
|
|
|
3
3
|
class SessionTest < ActiveSupport::TestCase
|
|
4
4
|
|
|
5
|
+
teardown do
|
|
6
|
+
QuoVadis.session_idle_timeout :lifetime
|
|
7
|
+
end
|
|
8
|
+
|
|
5
9
|
test 'expired?' do
|
|
6
10
|
refute QuoVadis::Session.new.expired?
|
|
7
11
|
assert QuoVadis::Session.new(lifetime_expires_at: 1.day.ago).expired?
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: quo_vadis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1
|
|
4
|
+
version: 2.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andy Stewart
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-08-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -90,17 +90,11 @@ files:
|
|
|
90
90
|
- app/controllers/quo_vadis/twofas_controller.rb
|
|
91
91
|
- app/mailers/quo_vadis/mailer.rb
|
|
92
92
|
- app/models/quo_vadis/account.rb
|
|
93
|
-
- app/models/quo_vadis/account_confirmation_token.rb
|
|
94
93
|
- app/models/quo_vadis/log.rb
|
|
95
94
|
- app/models/quo_vadis/password.rb
|
|
96
|
-
- app/models/quo_vadis/password_reset_token.rb
|
|
97
95
|
- app/models/quo_vadis/recovery_code.rb
|
|
98
96
|
- app/models/quo_vadis/session.rb
|
|
99
|
-
- app/models/quo_vadis/token.rb
|
|
100
97
|
- app/models/quo_vadis/totp.rb
|
|
101
|
-
- app/views/quo_vadis/confirmations/edit.html.erb
|
|
102
|
-
- app/views/quo_vadis/confirmations/edit_email.html.erb
|
|
103
|
-
- app/views/quo_vadis/confirmations/index.html.erb
|
|
104
98
|
- app/views/quo_vadis/confirmations/new.html.erb
|
|
105
99
|
- app/views/quo_vadis/logs/index.html.erb
|
|
106
100
|
- app/views/quo_vadis/mailer/account_confirmation.text.erb
|
|
@@ -114,7 +108,6 @@ files:
|
|
|
114
108
|
- app/views/quo_vadis/mailer/totp_setup_notification.text.erb
|
|
115
109
|
- app/views/quo_vadis/mailer/twofa_deactivated_notification.text.erb
|
|
116
110
|
- app/views/quo_vadis/password_resets/edit.html.erb
|
|
117
|
-
- app/views/quo_vadis/password_resets/index.html.erb
|
|
118
111
|
- app/views/quo_vadis/password_resets/new.html.erb
|
|
119
112
|
- app/views/quo_vadis/passwords/edit.html.erb
|
|
120
113
|
- app/views/quo_vadis/recovery_codes/challenge.html.erb
|
|
@@ -143,6 +136,7 @@ files:
|
|
|
143
136
|
- lib/quo_vadis/model.rb
|
|
144
137
|
- lib/quo_vadis/version.rb
|
|
145
138
|
- quo_vadis.gemspec
|
|
139
|
+
- test/README.md
|
|
146
140
|
- test/dummy/README.markdown
|
|
147
141
|
- test/dummy/Rakefile
|
|
148
142
|
- test/dummy/app/controllers/application_controller.rb
|
|
@@ -203,7 +197,6 @@ files:
|
|
|
203
197
|
- test/models/password_test.rb
|
|
204
198
|
- test/models/recovery_code_test.rb
|
|
205
199
|
- test/models/session_test.rb
|
|
206
|
-
- test/models/token_test.rb
|
|
207
200
|
- test/models/totp_test.rb
|
|
208
201
|
- test/quo_vadis_test.rb
|
|
209
202
|
- test/test_helper.rb
|
|
@@ -226,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
226
219
|
- !ruby/object:Gem::Version
|
|
227
220
|
version: '0'
|
|
228
221
|
requirements: []
|
|
229
|
-
rubygems_version: 3.
|
|
222
|
+
rubygems_version: 3.4.10
|
|
230
223
|
signing_key:
|
|
231
224
|
specification_version: 4
|
|
232
225
|
summary: Multifactor authentication for Rails 6 and 7.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module QuoVadis
|
|
4
|
-
class AccountConfirmationToken < Token
|
|
5
|
-
class << self
|
|
6
|
-
|
|
7
|
-
def expires_at
|
|
8
|
-
QuoVadis.account_confirmation_token_lifetime.from_now.to_i
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def data_for_hmac(data, account)
|
|
12
|
-
"#{data}-#{account.confirmed?}"
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module QuoVadis
|
|
4
|
-
class PasswordResetToken < Token
|
|
5
|
-
class << self
|
|
6
|
-
|
|
7
|
-
def expires_at
|
|
8
|
-
QuoVadis.password_reset_token_lifetime.from_now.to_i
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def data_for_hmac(data, account)
|
|
12
|
-
"#{data}-#{account.password.password_digest}"
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module QuoVadis
|
|
4
|
-
class Token
|
|
5
|
-
extend Hmacable
|
|
6
|
-
|
|
7
|
-
class << self
|
|
8
|
-
|
|
9
|
-
def generate(account)
|
|
10
|
-
public_data = "#{account.id}-#{expires_at}"
|
|
11
|
-
data = data_for_hmac public_data, account
|
|
12
|
-
"#{public_data}--#{compute_hmac(data)}"
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def find_account(token)
|
|
16
|
-
provided_public_data, provided_hmac = token.split '--'
|
|
17
|
-
id, expires_at = provided_public_data.split '-'
|
|
18
|
-
account = Account.find id
|
|
19
|
-
data = data_for_hmac provided_public_data, account
|
|
20
|
-
actual_hmac = compute_hmac data
|
|
21
|
-
return nil unless timing_safe_eql? provided_hmac, actual_hmac
|
|
22
|
-
return nil if expires_at.to_i < Time.current.to_i
|
|
23
|
-
account
|
|
24
|
-
rescue
|
|
25
|
-
nil
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
attr_reader :account
|
|
31
|
-
|
|
32
|
-
def expires_at
|
|
33
|
-
raise NotImplementedError
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def data_for_hmac(public_data, account)
|
|
37
|
-
raise NotImplementedError
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<h1>Account confirmation: change email</h1>
|
|
2
|
-
|
|
3
|
-
<p>Please update your email address.</p>
|
|
4
|
-
|
|
5
|
-
<%= form_with url: update_email_confirmations_path, method: :put do |f| %>
|
|
6
|
-
<p>
|
|
7
|
-
<%= f.label :email %>
|
|
8
|
-
<%= f.text_field :email, value: @email, inputmode: 'email', autocomplete: 'email' %>
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
<p>
|
|
12
|
-
<%= f.submit 'Update my email address and send me a new confirmation email' %>
|
|
13
|
-
</p>
|
|
14
|
-
<% end %>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<h1>Account confirmation</h1>
|
|
2
|
-
|
|
3
|
-
<% if @account %>
|
|
4
|
-
<p>We have sent an email to <%= @account.model.email %>.</p>
|
|
5
|
-
|
|
6
|
-
<p>Wrong address? <%= link_to 'Change it', edit_email_confirmations_path %>.</p>
|
|
7
|
-
|
|
8
|
-
<p>Didn't receive it? <%= button_to 'Get another one', resend_confirmations_path %></p>
|
|
9
|
-
|
|
10
|
-
<% else %>
|
|
11
|
-
<p>We have sent an email to you.</p>
|
|
12
|
-
|
|
13
|
-
<p><%= link_to 'Request a new email', new_confirmation_path %></p>
|
|
14
|
-
<% end %>
|
data/test/models/token_test.rb
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
require 'test_helper'
|
|
2
|
-
|
|
3
|
-
class TokenTest < ActiveSupport::TestCase
|
|
4
|
-
|
|
5
|
-
setup do
|
|
6
|
-
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
|
7
|
-
@account = u.qv_account
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
test 'account confirmation' do
|
|
12
|
-
token = QuoVadis::AccountConfirmationToken.generate @account
|
|
13
|
-
assert_match /^\d+-\d+--\h+$/, token
|
|
14
|
-
assert_equal @account, QuoVadis::AccountConfirmationToken.find_account(token)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
test 'account confirmation expired' do
|
|
18
|
-
token = QuoVadis::AccountConfirmationToken.generate @account
|
|
19
|
-
travel QuoVadis.account_confirmation_token_lifetime + 1.second
|
|
20
|
-
assert_nil QuoVadis::AccountConfirmationToken.find_account(token)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
test 'account confirmation already done' do
|
|
24
|
-
token = QuoVadis::AccountConfirmationToken.generate @account
|
|
25
|
-
@account.confirmed!
|
|
26
|
-
assert_nil QuoVadis::AccountConfirmationToken.find_account(token)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
test 'account confirmation token tampered with' do
|
|
30
|
-
assert_nil QuoVadis::AccountConfirmationToken.find_account(nil)
|
|
31
|
-
assert_nil QuoVadis::AccountConfirmationToken.find_account('')
|
|
32
|
-
assert_nil QuoVadis::AccountConfirmationToken.find_account('asdf')
|
|
33
|
-
|
|
34
|
-
token = QuoVadis::AccountConfirmationToken.generate @account
|
|
35
|
-
id, expires_at, hash = token.match(/^(\d+)-(\d+)--(\h+)$/).captures
|
|
36
|
-
fake_token = "#{id}-#{expires_at.to_i + 1}--#{hash}"
|
|
37
|
-
assert_nil QuoVadis::AccountConfirmationToken.find_account(fake_token)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
test 'password reset' do
|
|
42
|
-
token = QuoVadis::PasswordResetToken.generate @account
|
|
43
|
-
assert_match /^\d+-\d+--\h+$/, token
|
|
44
|
-
assert_equal @account, QuoVadis::PasswordResetToken.find_account(token)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
test 'password reset expired' do
|
|
48
|
-
token = QuoVadis::PasswordResetToken.generate @account
|
|
49
|
-
travel QuoVadis.password_reset_token_lifetime + 1.second
|
|
50
|
-
assert_nil QuoVadis::PasswordResetToken.find_account(token)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
test 'password reset already done' do
|
|
54
|
-
token = QuoVadis::PasswordResetToken.generate @account
|
|
55
|
-
@account.password.reset 'secretsecret', 'secretsecret'
|
|
56
|
-
assert_nil QuoVadis::PasswordResetToken.find_account(token)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
test 'password reset token tampered with' do
|
|
60
|
-
assert_nil QuoVadis::PasswordResetToken.find_account(nil)
|
|
61
|
-
assert_nil QuoVadis::PasswordResetToken.find_account('')
|
|
62
|
-
assert_nil QuoVadis::PasswordResetToken.find_account('asdf')
|
|
63
|
-
|
|
64
|
-
token = QuoVadis::PasswordResetToken.generate @account
|
|
65
|
-
id, expires_at, hash = token.match(/^(\d+)-(\d+)--(\h+)$/).captures
|
|
66
|
-
fake_token = "#{id}-#{expires_at.to_i + 1}--#{hash}"
|
|
67
|
-
assert_nil QuoVadis::PasswordResetToken.find_account(fake_token)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
end
|