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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +59 -33
  4. data/app/controllers/quo_vadis/confirmations_controller.rb +47 -1
  5. data/app/controllers/quo_vadis/password_resets_controller.rb +11 -8
  6. data/app/controllers/quo_vadis/passwords_controller.rb +4 -2
  7. data/app/controllers/quo_vadis/recovery_codes_controller.rb +1 -1
  8. data/app/controllers/quo_vadis/sessions_controller.rb +3 -3
  9. data/app/controllers/quo_vadis/totps_controller.rb +1 -1
  10. data/app/models/quo_vadis/account.rb +14 -0
  11. data/app/models/quo_vadis/log.rb +4 -2
  12. data/app/models/quo_vadis/password.rb +16 -0
  13. data/{test/dummy/app → app}/views/quo_vadis/confirmations/edit.html.erb +0 -0
  14. data/app/views/quo_vadis/confirmations/edit_email.html.erb +14 -0
  15. data/app/views/quo_vadis/confirmations/index.html.erb +14 -0
  16. data/{test/dummy/app → app}/views/quo_vadis/confirmations/new.html.erb +0 -0
  17. data/{test/dummy/app → app}/views/quo_vadis/logs/index.html.erb +3 -1
  18. data/{test/dummy/app → app}/views/quo_vadis/mailer/account_confirmation.text.erb +0 -0
  19. data/{test/dummy/app → app}/views/quo_vadis/mailer/email_change_notification.text.erb +0 -0
  20. data/{test/dummy/app → app}/views/quo_vadis/mailer/identifier_change_notification.text.erb +0 -0
  21. data/{test/dummy/app → app}/views/quo_vadis/mailer/password_change_notification.text.erb +0 -0
  22. data/{test/dummy/app → app}/views/quo_vadis/mailer/password_reset_notification.text.erb +0 -0
  23. data/{test/dummy/app → app}/views/quo_vadis/mailer/recovery_codes_generation_notification.text.erb +0 -0
  24. data/{test/dummy/app → app}/views/quo_vadis/mailer/reset_password.text.erb +0 -0
  25. data/{test/dummy/app → app}/views/quo_vadis/mailer/totp_reuse_notification.text.erb +0 -0
  26. data/{test/dummy/app → app}/views/quo_vadis/mailer/totp_setup_notification.text.erb +0 -0
  27. data/{test/dummy/app → app}/views/quo_vadis/mailer/twofa_deactivated_notification.text.erb +0 -0
  28. data/{test/dummy/app → app}/views/quo_vadis/password_resets/edit.html.erb +1 -9
  29. data/{test/dummy/app → app}/views/quo_vadis/password_resets/index.html.erb +0 -0
  30. data/{test/dummy/app → app}/views/quo_vadis/password_resets/new.html.erb +0 -0
  31. data/{test/dummy/app → app}/views/quo_vadis/passwords/edit.html.erb +1 -9
  32. data/{test/dummy/app → app}/views/quo_vadis/recovery_codes/challenge.html.erb +0 -0
  33. data/{test/dummy/app → app}/views/quo_vadis/recovery_codes/index.html.erb +0 -0
  34. data/{test/dummy/app → app}/views/quo_vadis/sessions/index.html.erb +0 -0
  35. data/{test/dummy/app → app}/views/quo_vadis/sessions/new.html.erb +0 -0
  36. data/{test/dummy/app → app}/views/quo_vadis/totps/challenge.html.erb +0 -0
  37. data/{test/dummy/app → app}/views/quo_vadis/totps/new.html.erb +0 -0
  38. data/{test/dummy/app → app}/views/quo_vadis/twofas/show.html.erb +0 -0
  39. data/config/locales/quo_vadis.en.yml +30 -1
  40. data/config/routes.rb +11 -5
  41. data/lib/generators/quo_vadis/install_generator.rb +1 -1
  42. data/lib/quo_vadis/controller.rb +3 -3
  43. data/lib/quo_vadis/defaults.rb +1 -1
  44. data/lib/quo_vadis/model.rb +8 -11
  45. data/lib/quo_vadis/version.rb +1 -1
  46. data/lib/quo_vadis.rb +5 -1
  47. data/test/dummy/app/controllers/sign_ups_controller.rb +1 -1
  48. data/test/dummy/config/initializers/quo_vadis.rb +0 -4
  49. data/test/integration/account_confirmation_test.rb +35 -2
  50. data/test/integration/logging_test.rb +14 -5
  51. data/test/integration/password_change_test.rb +16 -10
  52. data/test/integration/password_login_test.rb +14 -2
  53. data/test/integration/password_reset_test.rb +6 -6
  54. data/test/integration/totps_test.rb +1 -1
  55. data/test/models/account_test.rb +16 -0
  56. data/test/models/password_test.rb +16 -0
  57. data/test/models/recovery_code_test.rb +7 -1
  58. data/test/models/token_test.rb +1 -1
  59. metadata +29 -28
  60. 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
- <% if @password.errors.any? %>
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' %>
@@ -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', as: 'edit_password_reset'
16
- put '/pwd-reset/:token', to: 'password_resets#update', as: 'password_reset'
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
- get '/confirm/:token', to: 'confirmations#edit', as: 'edit_confirmation'
20
- put '/confirm/:token', to: 'confirmations#update', as: 'confirmation'
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__) / '..' / '..' / '..' / 'test' / 'dummy' / 'app' / 'views' / 'quo_vadis'
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
@@ -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.edit_confirmation_url(token)
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
@@ -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
@@ -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 :qv_build_account_and_password, on: :create
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
- # Enable a new-user form to set a password.
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
- self.qv_account ||= build_qv_account
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
- self.qv_account ||= build_qv_account
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
- private
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 }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- VERSION = '2.0.1'
4
+ VERSION = '2.1.2'
5
5
  end
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
- Account.find_by identifier: params[identifier]
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 sign_up_path(@user)
16
+ redirect_to quo_vadis.confirmations_path
17
17
  else
18
18
  redirect_to articles_path
19
19
  end
@@ -1,7 +1,3 @@
1
1
  QuoVadis.configure do
2
- # Cannot use the __Host- prefix in a non-SSL environment.
3
- # https://tools.ietf.org/html/draft-west-cookie-prefixes-05#section-3.2
4
- cookie_name 'qv'
5
-
6
2
  session_lifetime 1.week
7
3
  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
- user = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
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
- user = User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
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.change'
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', 'login.success'
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 :success
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 :success
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 :success
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: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'y')
43
- assert_response :success
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: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'xxxxxxxxxxxx')
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: '123456789abc', new_password: 'xxxxxxxxxxxx', new_password_confirmation: 'xxxxxxxxxxxx')
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 :success
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 :success
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
- assert_response :success
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.edit_password_reset_url(extract_token_from_email)
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 :success
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 :success
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
 
@@ -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
@@ -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.update password: 'secretsecret'
55
+ @account.password.reset 'secretsecret', 'secretsecret'
56
56
  assert_nil QuoVadis::PasswordResetToken.find_account(token)
57
57
  end
58
58