quo_vadis 2.0.2 → 2.1.3
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 +31 -0
- data/README.md +37 -30
- data/app/controllers/quo_vadis/password_resets_controller.rb +11 -8
- data/app/controllers/quo_vadis/passwords_controller.rb +4 -2
- data/app/controllers/quo_vadis/recovery_codes_controller.rb +1 -1
- data/app/controllers/quo_vadis/sessions_controller.rb +2 -2
- data/app/controllers/quo_vadis/totps_controller.rb +1 -1
- data/app/mailers/quo_vadis/mailer.rb +16 -16
- data/app/models/quo_vadis/account.rb +14 -0
- data/app/models/quo_vadis/log.rb +4 -2
- data/app/models/quo_vadis/password.rb +16 -0
- data/{test/dummy/app → app}/views/quo_vadis/confirmations/edit.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/confirmations/edit_email.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/confirmations/index.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/confirmations/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/logs/index.html.erb +3 -1
- data/{test/dummy/app → app}/views/quo_vadis/mailer/account_confirmation.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/email_change_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/identifier_change_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/password_change_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/password_reset_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/recovery_codes_generation_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/reset_password.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/totp_reuse_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/totp_setup_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/twofa_deactivated_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/password_resets/edit.html.erb +1 -9
- data/{test/dummy/app → app}/views/quo_vadis/password_resets/index.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/password_resets/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/passwords/edit.html.erb +1 -9
- data/{test/dummy/app → app}/views/quo_vadis/recovery_codes/challenge.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/recovery_codes/index.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/sessions/index.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/sessions/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/totps/challenge.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/totps/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/twofas/show.html.erb +0 -0
- data/config/locales/quo_vadis.en.yml +30 -1
- data/config/routes.rb +4 -4
- data/lib/generators/quo_vadis/install_generator.rb +1 -1
- data/lib/quo_vadis/controller.rb +2 -3
- data/lib/quo_vadis/defaults.rb +1 -1
- data/lib/quo_vadis/model.rb +8 -11
- data/lib/quo_vadis/version.rb +1 -1
- data/lib/quo_vadis.rb +6 -4
- data/test/dummy/config/initializers/quo_vadis.rb +0 -4
- data/test/integration/logging_test.rb +13 -4
- data/test/integration/password_change_test.rb +16 -10
- data/test/integration/password_login_test.rb +14 -2
- data/test/integration/password_reset_test.rb +6 -6
- data/test/integration/totps_test.rb +1 -1
- data/test/mailers/mailer_test.rb +49 -32
- data/test/models/account_test.rb +24 -2
- data/test/models/model_test.rb +4 -1
- data/test/models/password_test.rb +16 -0
- data/test/models/recovery_code_test.rb +7 -1
- data/test/models/token_test.rb +1 -1
- metadata +28 -28
@@ -31,7 +31,6 @@ en:
|
|
31
31
|
other: You have %{count} recovery codes left.
|
32
32
|
2fa:
|
33
33
|
invalidated: You have invalidated your 2FA credentials and recovery codes.
|
34
|
-
|
35
34
|
mailer:
|
36
35
|
password_reset:
|
37
36
|
subject: Change your password
|
@@ -46,6 +45,36 @@ en:
|
|
46
45
|
totp_reuse: Your two-factor authentication code was reused just now
|
47
46
|
twofa_deactivated: Two-factor authentication was deactivated just now
|
48
47
|
recovery_codes_generation: Recovery codes have been generated for your account
|
48
|
+
log:
|
49
|
+
action:
|
50
|
+
login:
|
51
|
+
success: Logged in
|
52
|
+
failure: Failed login attempt (incorrect password)
|
53
|
+
unknown: Failed login attempt (unknown identifier)
|
54
|
+
totp:
|
55
|
+
setup: TOTP set up for 2FA
|
56
|
+
success: Authenticated via TOTP
|
57
|
+
failure: Failed authentication attempt via TOTP
|
58
|
+
reuse: Failed attempt to reuse TOTP code
|
59
|
+
recovery_code:
|
60
|
+
success: Authenticated via 2FA recovery code
|
61
|
+
failure: Failed authentication attempt via 2FA recovery code
|
62
|
+
generate: Generated new 2FA recovery codes
|
63
|
+
2fa:
|
64
|
+
deactivated: Deactivated 2FA
|
65
|
+
identifier:
|
66
|
+
change: Changed identifier
|
67
|
+
email:
|
68
|
+
change: Changed email address
|
69
|
+
password:
|
70
|
+
change: Changed password
|
71
|
+
reset: Reset password
|
72
|
+
account:
|
73
|
+
confirmation: Confirmed account
|
74
|
+
logout:
|
75
|
+
self: Logged out
|
76
|
+
other: Logged out session remotely
|
77
|
+
revoke: Revoked access
|
49
78
|
activerecord:
|
50
79
|
errors:
|
51
80
|
models:
|
data/config/routes.rb
CHANGED
@@ -12,8 +12,8 @@ QuoVadis::Engine.routes.draw do
|
|
12
12
|
resource :password, only: [:edit, :update]
|
13
13
|
|
14
14
|
resources :password_resets, only: [:new, :create, :index]
|
15
|
-
get '/pwd-reset/:token', to: 'password_resets#edit',
|
16
|
-
put '/pwd-reset/:token', to: 'password_resets#update'
|
15
|
+
get '/pwd-reset/:token', to: 'password_resets#edit', as: 'password_reset'
|
16
|
+
put '/pwd-reset/:token', to: 'password_resets#update'
|
17
17
|
|
18
18
|
resources :confirmations, only: [:new, :create, :index] do
|
19
19
|
collection do
|
@@ -22,8 +22,8 @@ QuoVadis::Engine.routes.draw do
|
|
22
22
|
post :resend
|
23
23
|
end
|
24
24
|
end
|
25
|
-
get '/confirm/:token', to: 'confirmations#edit',
|
26
|
-
put '/confirm/:token', to: 'confirmations#update'
|
25
|
+
get '/confirm/:token', to: 'confirmations#edit', as: 'confirmation'
|
26
|
+
put '/confirm/:token', to: 'confirmations#update'
|
27
27
|
|
28
28
|
resources :totps, only: [:new, :create] do
|
29
29
|
collection do
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module QuoVadis
|
2
2
|
class InstallGenerator < Rails::Generators::Base
|
3
|
-
source_root Pathname.new(__dir__) / '..' / '..' / '..' / '
|
3
|
+
source_root Pathname.new(__dir__) / '..' / '..' / '..' / 'app' / 'views' / 'quo_vadis'
|
4
4
|
|
5
5
|
desc "Copy QuoVadis' views into your app."
|
6
6
|
def copy_views
|
data/lib/quo_vadis/controller.rb
CHANGED
@@ -17,9 +17,8 @@ module QuoVadis
|
|
17
17
|
|
18
18
|
def require_password_authentication
|
19
19
|
return if logged_in?
|
20
|
-
flash[:notice] = QuoVadis.translate 'flash.require_authentication'
|
21
20
|
session[:qv_bookmark] = request.original_fullpath
|
22
|
-
redirect_to quo_vadis.login_path
|
21
|
+
redirect_to quo_vadis.login_path, notice: QuoVadis.translate('flash.require_authentication')
|
23
22
|
end
|
24
23
|
alias_method :require_authentication, :require_password_authentication
|
25
24
|
|
@@ -88,7 +87,7 @@ module QuoVadis
|
|
88
87
|
|
89
88
|
def request_confirmation(model)
|
90
89
|
token = QuoVadis::AccountConfirmationToken.generate model.qv_account
|
91
|
-
QuoVadis.deliver :account_confirmation, email: model.email, url: quo_vadis.
|
90
|
+
QuoVadis.deliver :account_confirmation, {email: model.email, url: quo_vadis.confirmation_url(token)}
|
92
91
|
session[:account_pending_confirmation] = model.qv_account.id
|
93
92
|
|
94
93
|
flash[:notice] = QuoVadis.translate 'flash.confirmation.create'
|
data/lib/quo_vadis/defaults.rb
CHANGED
@@ -3,7 +3,7 @@ require 'active_support/core_ext'
|
|
3
3
|
QuoVadis.configure do
|
4
4
|
password_minimum_length 12
|
5
5
|
mask_ips false
|
6
|
-
cookie_name '__Host-qv'
|
6
|
+
cookie_name (Rails.env.production? ? '__Host-qv' : 'qv')
|
7
7
|
session_lifetime :session
|
8
8
|
session_lifetime_extend_to_end_of_day false
|
9
9
|
session_idle_timeout :lifetime
|
data/lib/quo_vadis/model.rb
CHANGED
@@ -14,11 +14,9 @@ module QuoVadis
|
|
14
14
|
|
15
15
|
has_one :qv_account, as: :model, class_name: 'QuoVadis::Account', dependent: :destroy, autosave: true
|
16
16
|
|
17
|
-
before_validation :
|
18
|
-
before_validation :qv_copy_identifier_to_account
|
17
|
+
before_validation :qv_copy_identifier_to_account, if: Proc.new { |m| m.qv_account }
|
19
18
|
|
20
|
-
|
21
|
-
validate :qv_copy_password_errors, on: :create
|
19
|
+
validate :qv_copy_password_errors, if: Proc.new { |m| m.qv_account&.password }
|
22
20
|
|
23
21
|
unless validators_on(identifier).any? { |v| ActiveRecord::Validations::UniquenessValidator === v }
|
24
22
|
raise NotImplementedError, <<~END
|
@@ -44,24 +42,23 @@ module QuoVadis
|
|
44
42
|
|
45
43
|
def password=(val)
|
46
44
|
@password = val
|
47
|
-
|
45
|
+
build_qv_account unless qv_account
|
48
46
|
raise PasswordExistsError if qv_account.password&.persisted?
|
49
47
|
(qv_account.password || qv_account.build_password).password = val
|
50
48
|
end
|
51
49
|
|
52
50
|
def password_confirmation=(val)
|
53
51
|
@password_confirmation = val
|
54
|
-
|
52
|
+
build_qv_account unless qv_account
|
55
53
|
(qv_account.password || qv_account.build_password).password_confirmation = val
|
56
54
|
end
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
def qv_build_account_and_password
|
61
|
-
self.qv_account ||= build_qv_account
|
62
|
-
qv_account.password || qv_account.build_password
|
56
|
+
def revoke_authentication_credentials
|
57
|
+
qv_account.revoke
|
63
58
|
end
|
64
59
|
|
60
|
+
private
|
61
|
+
|
65
62
|
def qv_copy_password_errors
|
66
63
|
qv_account.password.valid? # force qv_account.password to validate
|
67
64
|
qv_account.password.errors[:password ].each { |message| errors.add :password, message }
|
data/lib/quo_vadis/version.rb
CHANGED
data/lib/quo_vadis.rb
CHANGED
@@ -73,12 +73,14 @@ module QuoVadis
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def notify(action, params)
|
76
|
-
|
76
|
+
deliver(action, params, later: true)
|
77
77
|
end
|
78
78
|
|
79
|
-
def deliver(action, params)
|
80
|
-
mail = QuoVadis::Mailer
|
81
|
-
|
79
|
+
def deliver(action, params, later: QuoVadis.enqueue_transactional_emails)
|
80
|
+
mail = QuoVadis::Mailer
|
81
|
+
.with(params.merge(ip: QuoVadis::CurrentRequestDetails.ip, timestamp: Time.now))
|
82
|
+
.send(action)
|
83
|
+
later ?
|
82
84
|
mail.deliver_later :
|
83
85
|
mail.deliver_now
|
84
86
|
end
|
@@ -15,13 +15,13 @@ class LoggingTest < IntegrationTest
|
|
15
15
|
assert_response :success
|
16
16
|
|
17
17
|
assert_select 'tbody tr' do
|
18
|
-
assert_select 'td', 'password
|
18
|
+
assert_select 'td', 'Changed password'
|
19
19
|
assert_select 'td', '1.2.3.4'
|
20
20
|
assert_select 'td', 'foo: bar, baz: qux'
|
21
21
|
end
|
22
22
|
|
23
23
|
assert_select 'tbody tr' do
|
24
|
-
assert_select 'td', '
|
24
|
+
assert_select 'td', 'Logged in'
|
25
25
|
assert_select 'td', '127.0.0.1'
|
26
26
|
assert_select 'td', ''
|
27
27
|
end
|
@@ -157,7 +157,7 @@ class LoggingTest < IntegrationTest
|
|
157
157
|
test 'password.change' do
|
158
158
|
login
|
159
159
|
assert_log QuoVadis::Log::PASSWORD_CHANGE do
|
160
|
-
put quo_vadis.password_path(password: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'xxxxxxxxxxxx')
|
160
|
+
put quo_vadis.password_path(password: {password: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'xxxxxxxxxxxx'})
|
161
161
|
end
|
162
162
|
end
|
163
163
|
|
@@ -165,7 +165,7 @@ class LoggingTest < IntegrationTest
|
|
165
165
|
test 'password.reset' do
|
166
166
|
assert_difference 'QuoVadis::Log.count', 2 do
|
167
167
|
token = QuoVadis::PasswordResetToken.generate @account
|
168
|
-
put quo_vadis.password_reset_path(token, password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx')
|
168
|
+
put quo_vadis.password_reset_path(token, password: {password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx'})
|
169
169
|
end
|
170
170
|
assert_equal QuoVadis::Log::PASSWORD_RESET, QuoVadis::Log.first.action
|
171
171
|
assert_equal QuoVadis::Log::LOGIN_SUCCESS, log.action
|
@@ -201,6 +201,15 @@ class LoggingTest < IntegrationTest
|
|
201
201
|
end
|
202
202
|
|
203
203
|
|
204
|
+
test 'revoke' do
|
205
|
+
login_new_session
|
206
|
+
assert_log QuoVadis::Log::REVOKE do
|
207
|
+
QuoVadis::CurrentRequestDetails.ip = '127.0.0.1' # fake out the IP assertion
|
208
|
+
@account.revoke
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
|
204
213
|
private
|
205
214
|
|
206
215
|
def assert_log(action, metadata = {}, account = @account, &block)
|
@@ -18,29 +18,31 @@ class PasswordChangeTest < IntegrationTest
|
|
18
18
|
|
19
19
|
|
20
20
|
test 'incorrect password' do
|
21
|
-
put quo_vadis.password_path(password: 'x')
|
22
|
-
assert_response :
|
21
|
+
put quo_vadis.password_path(password: {password: 'x'})
|
22
|
+
assert_response :unprocessable_entity
|
23
23
|
assert_equal ['is incorrect'], password_instance.errors[:password]
|
24
24
|
end
|
25
25
|
|
26
26
|
|
27
27
|
test 'new password empty' do
|
28
|
-
put quo_vadis.password_path(password: '123456789abc', new_password: '')
|
29
|
-
assert_response :
|
28
|
+
put quo_vadis.password_path(password: {password: '123456789abc', new_password: ''})
|
29
|
+
assert_response :unprocessable_entity
|
30
30
|
assert_equal ["can't be blank"], password_instance.errors[:new_password]
|
31
31
|
end
|
32
32
|
|
33
33
|
|
34
34
|
test 'new password too short' do
|
35
|
-
put quo_vadis.password_path(password: '123456789abc', new_password: 'x')
|
36
|
-
assert_response :
|
35
|
+
put quo_vadis.password_path(password: {password: '123456789abc', new_password: 'x'})
|
36
|
+
assert_response :unprocessable_entity
|
37
37
|
assert_equal ["is too short (minimum is #{QuoVadis.password_minimum_length} characters)"], password_instance.errors[:new_password]
|
38
38
|
end
|
39
39
|
|
40
40
|
|
41
41
|
test 'new password confirmation does not match' do
|
42
|
-
put quo_vadis.password_path(password:
|
43
|
-
|
42
|
+
put quo_vadis.password_path(password: {
|
43
|
+
password: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'y'
|
44
|
+
})
|
45
|
+
assert_response :unprocessable_entity
|
44
46
|
assert_equal ["doesn't match Password"], password_instance.errors[:new_password_confirmation]
|
45
47
|
end
|
46
48
|
|
@@ -48,7 +50,9 @@ class PasswordChangeTest < IntegrationTest
|
|
48
50
|
test 'success' do
|
49
51
|
assert_emails 1 do
|
50
52
|
assert_session_replaced do
|
51
|
-
put quo_vadis.password_path(password:
|
53
|
+
put quo_vadis.password_path(password: {
|
54
|
+
password: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'xxxxxxxxxxxx'
|
55
|
+
})
|
52
56
|
assert_response :redirect
|
53
57
|
assert_equal 'Your password has been changed.', flash[:notice]
|
54
58
|
end
|
@@ -60,7 +64,9 @@ class PasswordChangeTest < IntegrationTest
|
|
60
64
|
desktop = session_login
|
61
65
|
phone = session_login
|
62
66
|
|
63
|
-
desktop.put quo_vadis.password_path(password:
|
67
|
+
desktop.put quo_vadis.password_path(password: {
|
68
|
+
password: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'xxxxxxxxxxxx'
|
69
|
+
})
|
64
70
|
desktop.follow_redirect!
|
65
71
|
assert desktop.controller.logged_in?
|
66
72
|
|
@@ -21,6 +21,7 @@ class PasswordLoginTest < IntegrationTest
|
|
21
21
|
|
22
22
|
test 'successful login redirects to original path' do
|
23
23
|
get also_secret_articles_path
|
24
|
+
refute_nil session[:qv_bookmark]
|
24
25
|
|
25
26
|
User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
26
27
|
post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
@@ -30,11 +31,22 @@ class PasswordLoginTest < IntegrationTest
|
|
30
31
|
end
|
31
32
|
|
32
33
|
|
34
|
+
test 'successful login does not redirect to login path' do
|
35
|
+
get quo_vadis.login_path
|
36
|
+
assert_nil session[:qv_bookmark]
|
37
|
+
|
38
|
+
User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
39
|
+
post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
40
|
+
|
41
|
+
assert_redirected_to after_login_path
|
42
|
+
end
|
43
|
+
|
44
|
+
|
33
45
|
test 'failed login' do
|
34
46
|
User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
35
47
|
post quo_vadis.login_path(email: 'bob@example.com', password: 'wrong')
|
36
48
|
|
37
|
-
assert_response :
|
49
|
+
assert_response :unprocessable_entity
|
38
50
|
assert_equal quo_vadis.login_path, path
|
39
51
|
end
|
40
52
|
|
@@ -42,7 +54,7 @@ class PasswordLoginTest < IntegrationTest
|
|
42
54
|
test 'unknown login' do
|
43
55
|
post quo_vadis.login_path(email: 'bob@example.com', password: 'wrong')
|
44
56
|
|
45
|
-
assert_response :
|
57
|
+
assert_response :unprocessable_entity
|
46
58
|
assert_equal quo_vadis.login_path, path
|
47
59
|
end
|
48
60
|
|
@@ -15,7 +15,7 @@ class PasswordResetTest < IntegrationTest
|
|
15
15
|
|
16
16
|
test 'unknown identifier' do
|
17
17
|
post quo_vadis.password_resets_path(email: 'foo@example.com')
|
18
|
-
|
18
|
+
assert_redirected_to quo_vadis.password_resets_path
|
19
19
|
assert_equal 'A link to change your password has been emailed to you.', flash[:notice]
|
20
20
|
end
|
21
21
|
|
@@ -53,10 +53,10 @@ class PasswordResetTest < IntegrationTest
|
|
53
53
|
assert_emails 1 do
|
54
54
|
post quo_vadis.password_resets_path(email: 'bob@example.com')
|
55
55
|
end
|
56
|
-
put quo_vadis.password_reset_path(extract_token_from_email, password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx')
|
56
|
+
put quo_vadis.password_reset_path(extract_token_from_email, password: {password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx'})
|
57
57
|
assert controller.logged_in?
|
58
58
|
|
59
|
-
get quo_vadis.
|
59
|
+
get quo_vadis.password_reset_url(extract_token_from_email)
|
60
60
|
assert_redirected_to quo_vadis.new_password_reset_path
|
61
61
|
assert_equal 'Either the link has expired or you have already reset your password.', flash[:alert]
|
62
62
|
end
|
@@ -70,11 +70,11 @@ class PasswordResetTest < IntegrationTest
|
|
70
70
|
end
|
71
71
|
|
72
72
|
assert_no_difference 'QuoVadis::Session.count' do
|
73
|
-
put quo_vadis.password_reset_path(extract_token_from_email, password: '', password_confirmation: '')
|
73
|
+
put quo_vadis.password_reset_path(extract_token_from_email, password: {password: '', password_confirmation: ''})
|
74
74
|
end
|
75
75
|
|
76
76
|
assert_equal digest, @user.qv_account.password.reload.password_digest
|
77
|
-
assert_response :
|
77
|
+
assert_response :unprocessable_entity
|
78
78
|
assert_equal quo_vadis.password_reset_path(extract_token_from_email), path
|
79
79
|
end
|
80
80
|
|
@@ -95,7 +95,7 @@ class PasswordResetTest < IntegrationTest
|
|
95
95
|
end
|
96
96
|
|
97
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')
|
98
|
+
put quo_vadis.password_reset_path(extract_token_from_email, password: {password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx'})
|
99
99
|
end
|
100
100
|
|
101
101
|
assert controller.logged_in?
|
@@ -74,7 +74,7 @@ class TotpsTest < IntegrationTest
|
|
74
74
|
|
75
75
|
post quo_vadis.authenticate_totps_path(totp: '123456')
|
76
76
|
refute QuoVadis::Session.last.second_factor_authenticated?
|
77
|
-
assert_response :
|
77
|
+
assert_response :unprocessable_entity
|
78
78
|
assert_equal 'Sorry, the code was incorrect. Please check your system clock is correct and try again.', flash[:alert]
|
79
79
|
end
|
80
80
|
|
data/test/mailers/mailer_test.rb
CHANGED
@@ -44,14 +44,16 @@ class MailerTest < ActionMailer::TestCase
|
|
44
44
|
|
45
45
|
|
46
46
|
test 'email change notification' do
|
47
|
-
email = QuoVadis::Mailer.with(
|
47
|
+
email = QuoVadis::Mailer.with(
|
48
|
+
email: 'Foo <foo@example.com>',
|
49
|
+
ip: '1.2.3.4',
|
50
|
+
timestamp: Time.now
|
51
|
+
).email_change_notification
|
48
52
|
|
49
53
|
# freeze_time
|
50
54
|
|
51
55
|
assert_emails 1 do
|
52
|
-
|
53
|
-
email.deliver_now
|
54
|
-
end
|
56
|
+
email.deliver_now
|
55
57
|
end
|
56
58
|
|
57
59
|
assert_equal ['foo@example.com'], email.to
|
@@ -62,14 +64,17 @@ class MailerTest < ActionMailer::TestCase
|
|
62
64
|
|
63
65
|
|
64
66
|
test 'identifier change notification' do
|
65
|
-
email = QuoVadis::Mailer.with(
|
67
|
+
email = QuoVadis::Mailer.with(
|
68
|
+
email: 'Foo <foo@example.com>',
|
69
|
+
identifier: 'email',
|
70
|
+
ip: '1.2.3.4',
|
71
|
+
timestamp: Time.now
|
72
|
+
).identifier_change_notification
|
66
73
|
|
67
74
|
# freeze_time
|
68
75
|
|
69
76
|
assert_emails 1 do
|
70
|
-
|
71
|
-
email.deliver_now
|
72
|
-
end
|
77
|
+
email.deliver_now
|
73
78
|
end
|
74
79
|
|
75
80
|
assert_equal ['foo@example.com'], email.to
|
@@ -80,14 +85,16 @@ class MailerTest < ActionMailer::TestCase
|
|
80
85
|
|
81
86
|
|
82
87
|
test 'password change notification' do
|
83
|
-
email = QuoVadis::Mailer.with(
|
88
|
+
email = QuoVadis::Mailer.with(
|
89
|
+
email: 'Foo <foo@example.com>',
|
90
|
+
ip: '1.2.3.4',
|
91
|
+
timestamp: Time.now
|
92
|
+
).password_change_notification
|
84
93
|
|
85
94
|
# freeze_time
|
86
95
|
|
87
96
|
assert_emails 1 do
|
88
|
-
|
89
|
-
email.deliver_now
|
90
|
-
end
|
97
|
+
email.deliver_now
|
91
98
|
end
|
92
99
|
|
93
100
|
assert_equal ['foo@example.com'], email.to
|
@@ -98,14 +105,16 @@ class MailerTest < ActionMailer::TestCase
|
|
98
105
|
|
99
106
|
|
100
107
|
test 'password reset notification' do
|
101
|
-
email = QuoVadis::Mailer.with(
|
108
|
+
email = QuoVadis::Mailer.with(
|
109
|
+
email: 'Foo <foo@example.com>',
|
110
|
+
ip: '1.2.3.4',
|
111
|
+
timestamp: Time.now
|
112
|
+
).password_reset_notification
|
102
113
|
|
103
114
|
# freeze_time
|
104
115
|
|
105
116
|
assert_emails 1 do
|
106
|
-
|
107
|
-
email.deliver_now
|
108
|
-
end
|
117
|
+
email.deliver_now
|
109
118
|
end
|
110
119
|
|
111
120
|
assert_equal ['foo@example.com'], email.to
|
@@ -116,14 +125,16 @@ class MailerTest < ActionMailer::TestCase
|
|
116
125
|
|
117
126
|
|
118
127
|
test 'totp setup notification' do
|
119
|
-
email = QuoVadis::Mailer.with(
|
128
|
+
email = QuoVadis::Mailer.with(
|
129
|
+
email: 'Foo <foo@example.com>',
|
130
|
+
ip: '1.2.3.4',
|
131
|
+
timestamp: Time.now
|
132
|
+
).totp_setup_notification
|
120
133
|
|
121
134
|
# freeze_time
|
122
135
|
|
123
136
|
assert_emails 1 do
|
124
|
-
|
125
|
-
email.deliver_now
|
126
|
-
end
|
137
|
+
email.deliver_now
|
127
138
|
end
|
128
139
|
|
129
140
|
assert_equal ['foo@example.com'], email.to
|
@@ -134,14 +145,16 @@ class MailerTest < ActionMailer::TestCase
|
|
134
145
|
|
135
146
|
|
136
147
|
test 'totp reuse notification' do
|
137
|
-
email = QuoVadis::Mailer.with(
|
148
|
+
email = QuoVadis::Mailer.with(
|
149
|
+
email: 'Foo <foo@example.com>',
|
150
|
+
ip: '1.2.3.4',
|
151
|
+
timestamp: Time.now
|
152
|
+
).totp_reuse_notification
|
138
153
|
|
139
154
|
# freeze_time
|
140
155
|
|
141
156
|
assert_emails 1 do
|
142
|
-
|
143
|
-
email.deliver_now
|
144
|
-
end
|
157
|
+
email.deliver_now
|
145
158
|
end
|
146
159
|
|
147
160
|
assert_equal ['foo@example.com'], email.to
|
@@ -152,14 +165,16 @@ class MailerTest < ActionMailer::TestCase
|
|
152
165
|
|
153
166
|
|
154
167
|
test '2fa deactivated notification' do
|
155
|
-
email = QuoVadis::Mailer.with(
|
168
|
+
email = QuoVadis::Mailer.with(
|
169
|
+
email: 'Foo <foo@example.com>',
|
170
|
+
ip: '1.2.3.4',
|
171
|
+
timestamp: Time.now
|
172
|
+
).twofa_deactivated_notification
|
156
173
|
|
157
174
|
# freeze_time
|
158
175
|
|
159
176
|
assert_emails 1 do
|
160
|
-
|
161
|
-
email.deliver_now
|
162
|
-
end
|
177
|
+
email.deliver_now
|
163
178
|
end
|
164
179
|
|
165
180
|
assert_equal ['foo@example.com'], email.to
|
@@ -170,14 +185,16 @@ class MailerTest < ActionMailer::TestCase
|
|
170
185
|
|
171
186
|
|
172
187
|
test 'recovery codes generation notification' do
|
173
|
-
email = QuoVadis::Mailer.with(
|
188
|
+
email = QuoVadis::Mailer.with(
|
189
|
+
email: 'Foo <foo@example.com>',
|
190
|
+
ip: '1.2.3.4',
|
191
|
+
timestamp: Time.now
|
192
|
+
).recovery_codes_generation_notification
|
174
193
|
|
175
194
|
# freeze_time
|
176
195
|
|
177
196
|
assert_emails 1 do
|
178
|
-
|
179
|
-
email.deliver_now
|
180
|
-
end
|
197
|
+
email.deliver_now
|
181
198
|
end
|
182
199
|
|
183
200
|
assert_equal ['foo@example.com'], email.to
|
data/test/models/account_test.rb
CHANGED
@@ -13,8 +13,11 @@ class AccountTest < ActiveSupport::TestCase
|
|
13
13
|
|
14
14
|
|
15
15
|
test 'notifies on identifier change when notifier is not email' do
|
16
|
+
freeze_time
|
16
17
|
p = Person.create! username: 'bob', email: 'bob@example.com', password: 'secretsecret'
|
17
|
-
assert_enqueued_email_with QuoVadis::Mailer,
|
18
|
+
assert_enqueued_email_with QuoVadis::Mailer,
|
19
|
+
:identifier_change_notification,
|
20
|
+
args: {email: 'bob@example.com', identifier: 'username', ip: nil, timestamp: Time.now} do
|
18
21
|
assert_enqueued_emails 1 do
|
19
22
|
p.update username: 'robert@example.com'
|
20
23
|
end
|
@@ -23,12 +26,31 @@ class AccountTest < ActiveSupport::TestCase
|
|
23
26
|
|
24
27
|
|
25
28
|
test 'does not notify on identifier change when notifier is email' do
|
29
|
+
freeze_time
|
26
30
|
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
27
|
-
assert_enqueued_email_with QuoVadis::Mailer,
|
31
|
+
assert_enqueued_email_with QuoVadis::Mailer,
|
32
|
+
:email_change_notification,
|
33
|
+
args: {email: 'bob@example.com', ip: nil, timestamp: Time.now} do
|
28
34
|
assert_enqueued_emails 1 do
|
29
35
|
u.update email: 'robert@example.com'
|
30
36
|
end
|
31
37
|
end
|
32
38
|
end
|
33
39
|
|
40
|
+
|
41
|
+
test 'revoke' do
|
42
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
43
|
+
account = u.qv_account
|
44
|
+
account.create_totp last_used_at: 1.minute.ago
|
45
|
+
account.generate_recovery_codes
|
46
|
+
|
47
|
+
u.revoke_authentication_credentials
|
48
|
+
account.reload
|
49
|
+
|
50
|
+
assert_nil account.password
|
51
|
+
assert_nil account.totp
|
52
|
+
assert_empty account.recovery_codes
|
53
|
+
assert_empty account.sessions
|
54
|
+
end
|
55
|
+
|
34
56
|
end
|
data/test/models/model_test.rb
CHANGED
@@ -58,8 +58,11 @@ class ModelTest < ActiveSupport::TestCase
|
|
58
58
|
|
59
59
|
|
60
60
|
test 'notifies on email change' do
|
61
|
+
freeze_time
|
61
62
|
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
62
|
-
assert_enqueued_email_with QuoVadis::Mailer,
|
63
|
+
assert_enqueued_email_with QuoVadis::Mailer,
|
64
|
+
:email_change_notification,
|
65
|
+
args: {email: 'bob@example.com', ip: nil, timestamp: Time.now} do
|
63
66
|
u.update email: 'robert@example.com'
|
64
67
|
end
|
65
68
|
end
|
@@ -46,6 +46,9 @@ class PasswordTest < ActiveSupport::TestCase
|
|
46
46
|
|
47
47
|
test 'model passes through password to quo_vadis' do
|
48
48
|
user = User.new name: 'bob', email: 'bob@example.com'
|
49
|
+
assert user.valid?
|
50
|
+
|
51
|
+
user = User.new name: 'bob', email: 'bob@example.com', password: ''
|
49
52
|
refute user.valid?
|
50
53
|
refute_empty user.errors[:password]
|
51
54
|
|
@@ -117,6 +120,19 @@ class PasswordTest < ActiveSupport::TestCase
|
|
117
120
|
end
|
118
121
|
|
119
122
|
|
123
|
+
test 'passwords can only be updated via #change and #reset' do
|
124
|
+
user = User.create! name: 'bob', email: 'bob@example.com', password: VALID_PASSWORD
|
125
|
+
pw = user.qv_account.password
|
126
|
+
|
127
|
+
refute pw.update password: 'secretsecret'
|
128
|
+
assert_equal ["must be updated via #change or #reset"], pw.errors[:password]
|
129
|
+
|
130
|
+
pw.password = VALID_PASSWORD
|
131
|
+
refute pw.save
|
132
|
+
assert_equal ["must be updated via #change or #reset"], pw.errors[:password]
|
133
|
+
end
|
134
|
+
|
135
|
+
|
120
136
|
test 'cascade destroy' do
|
121
137
|
user = User.create! name: 'bob', email: 'bob@example.com', password: VALID_PASSWORD
|
122
138
|
assert user.qv_account.persisted?
|