quo_vadis 2.0.1 → 2.1.2
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 +59 -33
- data/app/controllers/quo_vadis/confirmations_controller.rb +47 -1
- 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 +3 -3
- data/app/controllers/quo_vadis/totps_controller.rb +1 -1
- 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/app/views/quo_vadis/confirmations/edit_email.html.erb +14 -0
- data/app/views/quo_vadis/confirmations/index.html.erb +14 -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 +11 -5
- data/lib/generators/quo_vadis/install_generator.rb +1 -1
- data/lib/quo_vadis/controller.rb +3 -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 +5 -1
- data/test/dummy/app/controllers/sign_ups_controller.rb +1 -1
- data/test/dummy/config/initializers/quo_vadis.rb +0 -4
- data/test/integration/account_confirmation_test.rb +35 -2
- data/test/integration/logging_test.rb +14 -5
- 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/models/account_test.rb +16 -0
- 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 +29 -28
- data/test/dummy/app/views/quo_vadis/confirmations/index.html.erb +0 -5
@@ -1,14 +1,6 @@
|
|
1
1
|
<h1>Change password</h1>
|
2
2
|
|
3
|
-
|
4
|
-
<ul>
|
5
|
-
<% @password.errors.full_messages.each do |msg| %>
|
6
|
-
<li><%= msg %></li>
|
7
|
-
<% end %>
|
8
|
-
</ul>
|
9
|
-
<% end %>
|
10
|
-
|
11
|
-
<%= form_with url: password_path, method: :put do |f| %>
|
3
|
+
<%= form_with model: @password, url: password_path, method: :put do |f| %>
|
12
4
|
<p>
|
13
5
|
<%= f.label :password %>
|
14
6
|
<%= f.password_field :password, autocomplete: 'current-password' %>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -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,12 +12,18 @@ 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
|
-
resources :confirmations, only: [:new, :create, :index]
|
19
|
-
|
20
|
-
|
18
|
+
resources :confirmations, only: [:new, :create, :index] do
|
19
|
+
collection do
|
20
|
+
get :edit_email
|
21
|
+
put :update_email
|
22
|
+
post :resend
|
23
|
+
end
|
24
|
+
end
|
25
|
+
get '/confirm/:token', to: 'confirmations#edit', as: 'confirmation'
|
26
|
+
put '/confirm/:token', to: 'confirmations#update'
|
21
27
|
|
22
28
|
resources :totps, only: [:new, :create] do
|
23
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,8 @@ 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)
|
91
|
+
session[:account_pending_confirmation] = model.qv_account.id
|
92
92
|
|
93
93
|
flash[:notice] = QuoVadis.translate 'flash.confirmation.create'
|
94
94
|
end
|
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
@@ -45,8 +45,12 @@ module QuoVadis
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def find_account_by_identifier_in_params(params)
|
48
|
+
Account.find_by identifier: identifier_value_in_params(params)
|
49
|
+
end
|
50
|
+
|
51
|
+
def identifier_value_in_params(params)
|
48
52
|
identifier = detect_identifier params.keys
|
49
|
-
|
53
|
+
params[identifier]
|
50
54
|
end
|
51
55
|
|
52
56
|
# model - string class name, e.g. 'User'
|
@@ -13,7 +13,7 @@ class SignUpsController < ApplicationController
|
|
13
13
|
if @user.save
|
14
14
|
if QuoVadis.accounts_require_confirmation
|
15
15
|
request_confirmation @user
|
16
|
-
redirect_to
|
16
|
+
redirect_to quo_vadis.confirmations_path
|
17
17
|
else
|
18
18
|
redirect_to articles_path
|
19
19
|
end
|
@@ -6,6 +6,10 @@ class AccountConfirmationTest < IntegrationTest
|
|
6
6
|
QuoVadis.accounts_require_confirmation true
|
7
7
|
end
|
8
8
|
|
9
|
+
teardown do
|
10
|
+
QuoVadis.accounts_require_confirmation false
|
11
|
+
end
|
12
|
+
|
9
13
|
|
10
14
|
test 'new signup requiring confirmation' do
|
11
15
|
assert_emails 1 do
|
@@ -36,8 +40,37 @@ class AccountConfirmationTest < IntegrationTest
|
|
36
40
|
end
|
37
41
|
|
38
42
|
|
43
|
+
test 'new signup updates email' do
|
44
|
+
assert_emails 1 do
|
45
|
+
post sign_ups_path(user: {name: 'Bob', email: 'bob@example.com', password: '123456789abc'})
|
46
|
+
end
|
47
|
+
|
48
|
+
get quo_vadis.edit_email_confirmations_path
|
49
|
+
assert_response :success
|
50
|
+
|
51
|
+
# First email: changed-email notifier sent to original address
|
52
|
+
# Second email: confirmation email sent to new address
|
53
|
+
assert_emails 2 do
|
54
|
+
put quo_vadis.update_email_confirmations_path(email: 'bobby@example.com')
|
55
|
+
end
|
56
|
+
assert_equal ['bobby@example.com'], ActionMailer::Base.deliveries.last.to
|
57
|
+
assert_redirected_to quo_vadis.confirmations_path
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
test 'resend confirmation email in same session' do
|
62
|
+
assert_emails 1 do
|
63
|
+
post sign_ups_path(user: {name: 'Bob', email: 'bob@example.com', password: '123456789abc'})
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_emails 1 do
|
67
|
+
post quo_vadis.resend_confirmations_path
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
39
72
|
test 'resend confirmation email: valid identifier' do
|
40
|
-
|
73
|
+
User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
41
74
|
|
42
75
|
get quo_vadis.new_confirmation_path
|
43
76
|
assert_response :success
|
@@ -91,7 +124,7 @@ class AccountConfirmationTest < IntegrationTest
|
|
91
124
|
|
92
125
|
|
93
126
|
test 'accounts requiring confirmation cannot log in' do
|
94
|
-
|
127
|
+
User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
95
128
|
post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
|
96
129
|
assert_redirected_to quo_vadis.new_confirmation_path
|
97
130
|
assert_equal 'Please confirm your account first.', flash[:notice]
|
@@ -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
|
@@ -43,7 +43,7 @@ class LoggingTest < IntegrationTest
|
|
43
43
|
|
44
44
|
|
45
45
|
test 'login.unknown' do
|
46
|
-
assert_log QuoVadis::Log::LOGIN_UNKNOWN, {}, nil do
|
46
|
+
assert_log QuoVadis::Log::LOGIN_UNKNOWN, {'identifier' => 'wrong'}, nil do
|
47
47
|
post quo_vadis.login_path(email: 'wrong', password: 'wrong')
|
48
48
|
end
|
49
49
|
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/models/account_test.rb
CHANGED
@@ -31,4 +31,20 @@ class AccountTest < ActiveSupport::TestCase
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
|
35
|
+
test 'revoke' do
|
36
|
+
u = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
|
37
|
+
account = u.qv_account
|
38
|
+
account.create_totp last_used_at: 1.minute.ago
|
39
|
+
account.generate_recovery_codes
|
40
|
+
|
41
|
+
u.revoke_authentication_credentials
|
42
|
+
account.reload
|
43
|
+
|
44
|
+
assert_nil account.password
|
45
|
+
assert_nil account.totp
|
46
|
+
assert_empty account.recovery_codes
|
47
|
+
assert_empty account.sessions
|
48
|
+
end
|
49
|
+
|
34
50
|
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?
|
@@ -42,13 +42,19 @@ class RecoveryCodeTest < ActiveSupport::TestCase
|
|
42
42
|
test 'generate a fresh set of codes' do
|
43
43
|
account = @user.qv_account
|
44
44
|
codes = []
|
45
|
-
assert_difference 'QuoVadis::RecoveryCode.count', 5 do
|
45
|
+
assert_difference 'QuoVadis::RecoveryCode.count', (-1 + 5) do
|
46
46
|
codes = account.generate_recovery_codes
|
47
47
|
end
|
48
48
|
assert_equal 5, codes.length
|
49
|
+
assert_equal 5, account.recovery_codes.count
|
49
50
|
codes.each do |code|
|
50
51
|
assert_instance_of String, code
|
51
52
|
end
|
53
|
+
|
54
|
+
new_codes = account.generate_recovery_codes
|
55
|
+
assert_equal 5, new_codes.length
|
56
|
+
assert_equal 5, account.recovery_codes.count
|
57
|
+
refute_equal new_codes, codes
|
52
58
|
end
|
53
59
|
|
54
60
|
end
|
data/test/models/token_test.rb
CHANGED
@@ -52,7 +52,7 @@ class TokenTest < ActiveSupport::TestCase
|
|
52
52
|
|
53
53
|
test 'password reset already done' do
|
54
54
|
token = QuoVadis::PasswordResetToken.generate @account
|
55
|
-
@account.password.
|
55
|
+
@account.password.reset 'secretsecret', 'secretsecret'
|
56
56
|
assert_nil QuoVadis::PasswordResetToken.find_account(token)
|
57
57
|
end
|
58
58
|
|