quo_vadis 2.0.1 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|