quo_vadis 2.1.11 → 2.2.0

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +50 -99
  4. data/app/controllers/quo_vadis/confirmations_controller.rb +26 -61
  5. data/app/controllers/quo_vadis/password_resets_controller.rb +64 -32
  6. data/app/controllers/quo_vadis/sessions_controller.rb +0 -5
  7. data/app/mailers/quo_vadis/mailer.rb +2 -2
  8. data/app/models/quo_vadis/account.rb +32 -0
  9. data/app/models/quo_vadis/password.rb +6 -1
  10. data/app/views/quo_vadis/confirmations/new.html.erb +19 -7
  11. data/app/views/quo_vadis/mailer/account_confirmation.text.erb +2 -3
  12. data/app/views/quo_vadis/mailer/reset_password.text.erb +2 -2
  13. data/app/views/quo_vadis/password_resets/edit.html.erb +13 -1
  14. data/app/views/quo_vadis/password_resets/new.html.erb +1 -1
  15. data/config/locales/quo_vadis.en.yml +9 -7
  16. data/config/routes.rb +4 -12
  17. data/lib/quo_vadis/controller.rb +20 -10
  18. data/lib/quo_vadis/defaults.rb +2 -2
  19. data/lib/quo_vadis/version.rb +1 -1
  20. data/lib/quo_vadis.rb +2 -2
  21. data/test/dummy/app/controllers/sign_ups_controller.rb +2 -11
  22. data/test/fixtures/quo_vadis/mailer/account_confirmation.text +2 -3
  23. data/test/fixtures/quo_vadis/mailer/reset_password.text +2 -2
  24. data/test/integration/account_confirmation_test.rb +42 -86
  25. data/test/integration/controller_test.rb +8 -8
  26. data/test/integration/logging_test.rb +31 -7
  27. data/test/integration/password_login_test.rb +1 -1
  28. data/test/integration/password_reset_test.rb +89 -54
  29. data/test/mailers/mailer_test.rb +2 -2
  30. data/test/models/account_test.rb +48 -0
  31. data/test/models/session_test.rb +4 -0
  32. metadata +2 -10
  33. data/app/models/quo_vadis/account_confirmation_token.rb +0 -17
  34. data/app/models/quo_vadis/password_reset_token.rb +0 -17
  35. data/app/models/quo_vadis/token.rb +0 -42
  36. data/app/views/quo_vadis/confirmations/edit.html.erb +0 -10
  37. data/app/views/quo_vadis/confirmations/edit_email.html.erb +0 -14
  38. data/app/views/quo_vadis/confirmations/index.html.erb +0 -14
  39. data/app/views/quo_vadis/password_resets/index.html.erb +0 -5
  40. data/test/models/token_test.rb +0 -70
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rotp'
4
+
3
5
  module QuoVadis
4
6
  class Account < ActiveRecord::Base
5
7
 
@@ -18,6 +20,26 @@ module QuoVadis
18
20
  after_update :log_identifier_change, if: :saved_change_to_identifier?
19
21
  after_update :notify_identifier_change, if: :saved_change_to_identifier?
20
22
 
23
+ scope :unconfirmed, -> { where confirmed_at: nil }
24
+
25
+ def otp_for_confirmation(counter)
26
+ hotp_for_confirmation.at(counter)
27
+ end
28
+
29
+ # If the `otp` is valid for the `counter`, confirms the account and returns truthy.
30
+ # Otherwise returns falsey.
31
+ def confirm(otp, counter)
32
+ hotp_for_confirmation.verify(otp, counter) && confirmed!
33
+ end
34
+
35
+ def otp_for_password_reset(counter)
36
+ hotp_for_password_reset.at(counter)
37
+ end
38
+
39
+ def verify_password_reset(otp, counter)
40
+ hotp_for_password_reset.verify(otp, counter)
41
+ end
42
+
21
43
  def confirmed?
22
44
  confirmed_at.present?
23
45
  end
@@ -51,6 +73,16 @@ module QuoVadis
51
73
 
52
74
  private
53
75
 
76
+ def hotp_for_confirmation
77
+ key = ROTP::Base32.encode("#{id}-#{Rails.application.secret_key_base}")
78
+ ROTP::HOTP.new(key)
79
+ end
80
+
81
+ def hotp_for_password_reset
82
+ key = ROTP::Base32.encode("#{id}-#{password.password_digest}")
83
+ ROTP::HOTP.new(key)
84
+ end
85
+
54
86
  def log_identifier_change
55
87
  from, to = saved_change_to_identifier
56
88
  Log.create(
@@ -46,7 +46,12 @@ module QuoVadis
46
46
 
47
47
  self.password = new_plaintext
48
48
  self.password_confirmation = new_plaintext_confirmation
49
- save
49
+ if save
50
+ # Logout account's sessions because password has changed.
51
+ # Assumes model is not logged in.
52
+ account.sessions.destroy_all
53
+ true
54
+ end
50
55
  end
51
56
 
52
57
  private
@@ -1,16 +1,28 @@
1
- <h1>Resend confirmation instructions</h1>
1
+ <h1>Account confirmation</h1>
2
2
 
3
- <%# Note that in this example the User identifier is :email. %>
3
+ <p>We have sent a confirmation code to <%= @account.model.email %>.</p>
4
4
 
5
- <p>If you didn't receive the confirmation email, please enter your email address and we'll send you another one.</p>
5
+ <!-- flash could go here -->
6
6
 
7
- <%= form_with url: confirmations_path, method: :post do |f| %>
7
+ <%= form_with url: confirm_path, method: :post do %>
8
8
  <p>
9
- <%= f.label :email %>
10
- <%= f.text_field :email, inputmode: 'email', autocomplete: 'email' %>
9
+ <label for="otp">Your confirmation code:</label>
10
+ <input
11
+ name="otp"
12
+ id="otp"
13
+ type="text"
14
+ inputmode="numeric"
15
+ pattern="\d*"
16
+ autofocus="true"
17
+ autocomplete="one-time-password"
18
+ maxlength="6">
11
19
  </p>
12
20
 
13
21
  <p>
14
- <%= f.submit %>
22
+ <button type="submit">Confirm me!</button>
15
23
  </p>
16
24
  <% end %>
25
+
26
+ <p>
27
+ <%= button_to "Send me a new confirmation code", send_confirmation_path %>
28
+ </p>
@@ -1,4 +1,3 @@
1
- You can confirm your account here:
2
-
3
- <%= @url %>
1
+ This is your confirmation code:
4
2
 
3
+ <%= @otp %>
@@ -1,4 +1,4 @@
1
- You can reset your password here:
1
+ Here is your code so you can reset your password:
2
2
 
3
- <%= @url %>
3
+ <%= @otp %>
4
4
 
@@ -1,6 +1,18 @@
1
1
  <h1>Reset password</h1>
2
2
 
3
- <%= form_with model: @password, url: password_reset_path(params[:token]), method: :put do |f| %>
3
+ <%= form_with model: @password, url: password_reset_path, method: :put do |f| %>
4
+ <p>
5
+ <%= f.label :otp, 'Your password reset code' %>
6
+ <%= f.text_field :otp,
7
+ value: params.dig(:password, :otp),
8
+ inputmode: 'numeric',
9
+ pattern: '\d*',
10
+ autofocus: true,
11
+ autocomplete: 'one-time-password',
12
+ maxlength: 6 %>
13
+ </p>
14
+
15
+
4
16
  <p>
5
17
  <%= f.label :password %>
6
18
  <%= f.password_field :password, autocomplete: 'new-password' %>
@@ -1,6 +1,6 @@
1
1
  <h1>Reset password</h1>
2
2
 
3
- <%= form_with url: password_resets_path, method: :post do |f| %>
3
+ <%= form_with url: password_reset_path, method: :post do |f| %>
4
4
  <p>
5
5
  <%= f.label :email %>
6
6
  <%= f.text_field :email, inputmode: 'email', autocomplete: 'email' %>
@@ -11,15 +11,17 @@ en:
11
11
  password:
12
12
  update: Your password has been changed.
13
13
  password_reset:
14
- create: A link to change your password has been emailed to you.
15
- unknown: Either the link has expired or you have already reset your password.
14
+ create: Please check your email for your reset code.
15
+ unknown: Please check your email for your reset code.
16
+ expired: Your reset code has expired. Please request another one.
17
+ invalid: Sorry, the code was incorrect. Please try again.
16
18
  reset: Your password has been changed and you are logged in.
17
19
  confirmation:
18
- create: A link to confirm your account has been emailed to you.
19
- required: Please confirm your account first.
20
- identifier: Sorry, your account could not be found. Please try again.
21
- unknown: Either the link has expired or your account has already been confirmed.
22
- confirmed: Thanks for confirming your account. You are now logged in.
20
+ unknown: You have already confirmed your account.
21
+ sent: Please check your email for your confirmation code.
22
+ expired: Your confirmation code has expired. Please request another one.
23
+ invalid: Sorry, the code was incorrect. Please try again.
24
+ confirmed: Thanks for confirming your account.
23
25
  totp:
24
26
  unverified: Sorry, the code was incorrect. Please check your system clock is correct and try again.
25
27
  setup: Please set up two factor authentication.
data/config/routes.rb CHANGED
@@ -11,19 +11,11 @@ QuoVadis::Engine.routes.draw do
11
11
 
12
12
  resource :password, only: [:edit, :update]
13
13
 
14
- resources :password_resets, only: [:new, :create, :index]
15
- get '/pwd-reset/:token', to: 'password_resets#edit', as: 'password_reset'
16
- put '/pwd-reset/:token', to: 'password_resets#update'
14
+ resource :password_reset, only: [:new, :create, :edit, :update]
17
15
 
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'
16
+ get '/confirm', to: 'confirmations#new'
17
+ post '/confirm', to: 'confirmations#create'
18
+ post '/confirm/send', to: 'confirmations#resend', as: 'send_confirmation'
27
19
 
28
20
  resources :totps, only: [:new, :create] do
29
21
  collection do
@@ -16,7 +16,13 @@ module QuoVadis
16
16
 
17
17
 
18
18
  def require_password_authentication
19
- return if logged_in?
19
+ if logged_in?
20
+ if QuoVadis.accounts_require_confirmation && !authenticated_model.qv_account.confirmed?
21
+ qv.request_confirmation authenticated_model
22
+ redirect_to quo_vadis.confirm_path
23
+ end
24
+ return
25
+ end
20
26
  session[:qv_bookmark] = request.original_fullpath
21
27
  redirect_to quo_vadis.login_path, notice: QuoVadis.translate('flash.require_authentication')
22
28
  end
@@ -85,15 +91,6 @@ module QuoVadis
85
91
  end
86
92
 
87
93
 
88
- def request_confirmation(model)
89
- token = QuoVadis::AccountConfirmationToken.generate model.qv_account
90
- QuoVadis.deliver :account_confirmation, {email: model.email, url: quo_vadis.confirmation_url(token)}
91
- session[:account_pending_confirmation] = model.qv_account.id
92
-
93
- flash[:notice] = QuoVadis.translate 'flash.confirmation.create'
94
- end
95
-
96
-
97
94
  def qv
98
95
  @qv_wrapper ||= QuoVadisWrapper.new self
99
96
  end
@@ -143,6 +140,19 @@ module QuoVadis
143
140
  old_session.each { |k,v| rails_session[k] = v }
144
141
  end
145
142
 
143
+ def request_confirmation(model)
144
+ rails_session[:account_pending_confirmation] = model.qv_account.id
145
+
146
+ expiration = QuoVadis.account_confirmation_otp_lifetime.from_now.to_i
147
+ rails_session[:account_confirmation_expires_at] = expiration
148
+
149
+ otp = model.qv_account.otp_for_confirmation(expiration)
150
+
151
+ QuoVadis.deliver :account_confirmation, {email: model.email, otp: otp}
152
+
153
+ controller.flash[:notice] = QuoVadis.translate 'flash.confirmation.sent'
154
+ end
155
+
146
156
  # Assumes user is logged in.
147
157
  def second_factor_required?
148
158
  QuoVadis.two_factor_authentication_mandatory || authenticated_model.qv_account.has_two_factors?
@@ -7,9 +7,9 @@ QuoVadis.configure do
7
7
  session_lifetime :session
8
8
  session_lifetime_extend_to_end_of_day false
9
9
  session_idle_timeout :lifetime
10
- password_reset_token_lifetime 10.minutes
10
+ password_reset_otp_lifetime 10.minutes
11
11
  accounts_require_confirmation false
12
- account_confirmation_token_lifetime 10.minutes
12
+ account_confirmation_otp_lifetime 10.minutes
13
13
  mailer_superclass 'ApplicationMailer'
14
14
  mail_headers ({ from: 'Example App <support@example.com>' })
15
15
  enqueue_transactional_emails true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- VERSION = '2.1.11'
4
+ VERSION = '2.2.0'
5
5
  end
data/lib/quo_vadis.rb CHANGED
@@ -24,11 +24,11 @@ module QuoVadis
24
24
  :session_lifetime, # :session | ActiveSupport::Duration | [integer] seconds
25
25
  :session_lifetime_extend_to_end_of_day,# true | false
26
26
  :session_idle_timeout, # :lifetime | ActiveSupport::Duration | [integer] seconds
27
- :password_reset_token_lifetime, # ActiveSupport::Duration | [integer] seconds
27
+ :password_reset_otp_lifetime, # ActiveSupport::Duration | [integer] seconds
28
28
  :mailer_superclass, # string
29
29
  :mail_headers, # hash
30
30
  :accounts_require_confirmation, # true | false
31
- :account_confirmation_token_lifetime, # ActiveSupport::Duration | [integer] seconds
31
+ :account_confirmation_otp_lifetime, # ActiveSupport::Duration | [integer] seconds
32
32
  :enqueue_transactional_emails, # true | false
33
33
  :app_name, # string
34
34
  :two_factor_authentication_mandatory, # true | false
@@ -1,8 +1,6 @@
1
1
  # To test sign-ups with confirmation (activation / verification)
2
2
  class SignUpsController < ApplicationController
3
3
 
4
- around_action :toggle_confirmation
5
-
6
4
  def new
7
5
  @user = User.new
8
6
  end
@@ -11,9 +9,9 @@ class SignUpsController < ApplicationController
11
9
  def create
12
10
  @user = User.new user_params
13
11
  if @user.save
12
+ login @user
14
13
  if QuoVadis.accounts_require_confirmation
15
- request_confirmation @user
16
- redirect_to quo_vadis.confirmations_path
14
+ redirect_to secret_articles_path
17
15
  else
18
16
  redirect_to articles_path
19
17
  end
@@ -37,11 +35,4 @@ class SignUpsController < ApplicationController
37
35
  params.require(:user).permit(:name, :email, :password, :password_confirmation)
38
36
  end
39
37
 
40
- def toggle_confirmation
41
- QuoVadis.accounts_require_confirmation true
42
- yield
43
- ensure
44
- QuoVadis.accounts_require_confirmation false
45
- end
46
-
47
38
  end
@@ -1,4 +1,3 @@
1
- You can confirm your account here:
2
-
3
- http://example.com/confirm/123abc
1
+ This is your confirmation code:
4
2
 
3
+ 271828
@@ -1,4 +1,4 @@
1
- You can reset your password here:
1
+ Here is your code so you can reset your password:
2
2
 
3
- http://example.com/pwd-reset/123abc
3
+ 314159
4
4
 
@@ -14,136 +14,92 @@ class AccountConfirmationTest < IntegrationTest
14
14
  test 'new signup requiring confirmation' do
15
15
  assert_emails 1 do
16
16
  post sign_ups_path(user: {name: 'Bob', email: 'bob@example.com', password: '123456789abc'})
17
+ refute QuoVadis::Account.last.confirmed?
18
+ assert controller.logged_in?
19
+
20
+ # verify response
21
+ assert_redirected_to '/articles/secret'
22
+ follow_redirect!
23
+ assert_redirected_to '/confirm'
24
+ follow_redirect!
25
+ assert_equal 'Please check your email for your confirmation code.', flash[:notice]
17
26
  end
18
- refute QuoVadis::Account.last.confirmed?
19
-
20
- # verify response
21
- assert_response :redirect
22
- follow_redirect!
23
- assert_equal 'A link to confirm your account has been emailed to you.', flash[:notice]
24
27
 
25
- # click link in email
26
- url = extract_url_from_email
27
- get url
28
- assert_response :success
29
- action_path = extract_path_from_email
30
- assert_select "form[action='#{action_path}']"
31
-
32
- # click button on confirmation page
33
- put action_path
28
+ # type in confirmation code from email
29
+ code = extract_code_from_email
30
+ post quo_vadis.confirm_path(otp: code)
34
31
 
35
32
  # verify logged in
36
33
  assert_redirected_to '/sign_ups/confirmed'
37
34
  follow_redirect!
38
35
  assert_redirected_to '/articles/secret'
39
- assert_equal 'Thanks for confirming your account. You are now logged in.', flash[:notice]
40
- assert controller.logged_in?
36
+ assert_equal 'Thanks for confirming your account.', flash[:notice]
41
37
  assert QuoVadis::Account.last.confirmed?
42
38
  end
43
39
 
44
40
 
45
- test 'new signup updates email' do
46
- assert_emails 1 do
47
- post sign_ups_path(user: {name: 'Bob', email: 'bob@example.com', password: '123456789abc'})
48
- end
49
-
50
- get quo_vadis.edit_email_confirmations_path
51
- assert_response :success
52
-
53
- # First email: changed-email notifier sent to original address
54
- # Second email: confirmation email sent to new address
55
- assert_emails 2 do
56
- put quo_vadis.update_email_confirmations_path(email: 'bobby@example.com')
57
- end
58
- assert_equal ['bobby@example.com'], ActionMailer::Base.deliveries.last.to
59
- assert_redirected_to quo_vadis.confirmations_path
60
- end
61
-
62
-
63
- test 'resend confirmation email in same session' do
41
+ test 'resend confirmation email' do
64
42
  assert_emails 1 do
65
43
  post sign_ups_path(user: {name: 'Bob', email: 'bob@example.com', password: '123456789abc'})
44
+ assert_redirected_to '/articles/secret'
45
+ follow_redirect!
66
46
  end
67
47
 
68
48
  assert_emails 1 do
69
- post quo_vadis.resend_confirmations_path
49
+ post quo_vadis.send_confirmation_path
70
50
  end
71
51
  end
72
52
 
73
53
 
74
- test 'resend confirmation email: valid identifier' do
75
- User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
76
-
77
- get quo_vadis.new_confirmation_path
78
- assert_response :success
79
-
80
- assert_emails 1 do
81
- post quo_vadis.confirmations_path(email: 'bob@example.com')
82
- end
83
-
84
- assert_redirected_to '/confirmations'
85
- assert_equal 'A link to confirm your account has been emailed to you.', flash[:notice]
86
- end
87
-
88
-
89
- test 'resend confirmation email: unknown identifier' do
90
- assert_no_emails do
91
- post quo_vadis.confirmations_path(email: 'bob@example.com')
92
- end
93
-
94
- assert_redirected_to quo_vadis.new_confirmation_path
95
- assert_equal 'Sorry, your account could not be found. Please try again.', flash[:alert]
96
- end
97
-
98
-
99
- test 'reusing a token' do
54
+ test 'cannot reuse a confirmation code' do
100
55
  assert_emails 1 do
101
56
  post sign_ups_path(user: {name: 'Bob', email: 'bob@example.com', password: '123456789abc'})
57
+ follow_redirect!
102
58
  end
103
59
 
104
- put extract_path_from_email
60
+ code = extract_code_from_email
61
+ post quo_vadis.confirm_path(otp: code)
62
+ assert QuoVadis::Account.last.confirmed?
63
+ assert_equal 'Thanks for confirming your account.', flash[:notice]
105
64
 
65
+ post quo_vadis.confirm_path(otp: code)
66
+ assert_equal 'You have already confirmed your account.', flash[:alert]
106
67
  assert_redirected_to '/sign_ups/confirmed'
107
- follow_redirect!
108
- assert_redirected_to '/articles/secret'
109
- assert_equal 'Thanks for confirming your account. You are now logged in.', flash[:notice]
110
-
111
- put extract_path_from_email
112
- assert_redirected_to quo_vadis.new_confirmation_path
113
- assert_equal 'Either the link has expired or your account has already been confirmed.', flash[:alert]
114
68
  end
115
69
 
116
70
 
117
- test 'token expired' do
71
+ test 'confirmation code expired' do
118
72
  assert_emails 1 do
119
73
  post sign_ups_path(user: {name: 'Bob', email: 'bob@example.com', password: '123456789abc'})
74
+ follow_redirect!
120
75
  end
121
76
 
122
- travel QuoVadis.account_confirmation_token_lifetime + 1.minute
123
- get extract_url_from_email
77
+ travel QuoVadis.account_confirmation_otp_lifetime + 1.minute
124
78
 
125
- assert_redirected_to quo_vadis.new_confirmation_path
126
- assert_equal 'Either the link has expired or your account has already been confirmed.', flash[:alert]
79
+ code = extract_code_from_email
80
+ post quo_vadis.confirm_path(otp: code)
81
+ refute QuoVadis::Account.last.confirmed?
82
+ assert_equal 'Your confirmation code has expired. Please request another one.', flash[:alert]
83
+ assert_redirected_to quo_vadis.confirm_path
127
84
  end
128
85
 
129
86
 
130
- test 'accounts requiring confirmation cannot log in' do
87
+ test 'accounts requiring confirmation can log in but have to confirm' do
131
88
  User.create! name: 'bob', email: 'bob@example.com', password: '123456789abc'
132
89
  post quo_vadis.login_path(email: 'bob@example.com', password: '123456789abc')
133
- assert_redirected_to quo_vadis.new_confirmation_path
134
- assert_equal 'Please confirm your account first.', flash[:notice]
135
- refute controller.logged_in?
90
+ assert_redirected_to '/articles/secret'
91
+ follow_redirect!
92
+ assert_redirected_to quo_vadis.confirm_path
93
+ follow_redirect!
94
+ assert controller.logged_in?
95
+ assert_equal 'Please check your email for your confirmation code.', flash[:notice]
136
96
  end
137
97
 
138
98
 
139
99
  private
140
100
 
141
- def extract_url_from_email
142
- ActionMailer::Base.deliveries.last.decoded[%r{^http://.*$}, 0]
143
- end
144
-
145
- def extract_path_from_email
146
- extract_url_from_email.sub 'http://www.example.com', ''
101
+ def extract_code_from_email
102
+ ActionMailer::Base.deliveries.last.decoded[%r{\d{6}}, 0]
147
103
  end
148
104
 
149
105
  end
@@ -206,14 +206,14 @@ class ControllerTest < IntegrationTest
206
206
  end
207
207
 
208
208
 
209
- test 'request_confirmation' do
210
- get articles_path
211
-
212
- assert_emails 1 do
213
- controller.request_confirmation User.last
214
- end
215
- assert_equal 'A link to confirm your account has been emailed to you.', flash[:notice]
216
- end
209
+ # test 'request_confirmation' do
210
+ # get articles_path
211
+
212
+ # assert_emails 1 do
213
+ # controller.request_confirmation User.last
214
+ # end
215
+ # assert_equal 'A link to confirm your account has been emailed to you.', flash[:notice]
216
+ # end
217
217
 
218
218
 
219
219
  test 'session lifetime exceeded' do
@@ -163,22 +163,42 @@ class LoggingTest < IntegrationTest
163
163
 
164
164
 
165
165
  test 'password.reset' do
166
+ assert_emails 1 do
167
+ post quo_vadis.password_reset_path(email: 'bob@example.com')
168
+ follow_redirect!
169
+ end
170
+
166
171
  assert_difference 'QuoVadis::Log.count', 2 do
167
- token = QuoVadis::PasswordResetToken.generate @account
168
- put quo_vadis.password_reset_path(token, password: {password: 'xxxxxxxxxxxx', password_confirmation: 'xxxxxxxxxxxx'})
172
+ put quo_vadis.password_reset_path(password: {
173
+ otp: extract_code_from_email,
174
+ password: 'secretsecret',
175
+ password_confirmation: 'secretsecret',
176
+ })
169
177
  end
178
+
170
179
  assert_equal QuoVadis::Log::PASSWORD_RESET, QuoVadis::Log.first.action
171
180
  assert_equal QuoVadis::Log::LOGIN_SUCCESS, log.action
172
181
  end
173
182
 
174
183
 
175
184
  test 'account.confirmation' do
176
- assert_difference 'QuoVadis::Log.count', 2 do
177
- token = QuoVadis::AccountConfirmationToken.generate @account
178
- put quo_vadis.confirmation_path(token)
185
+ QuoVadis.accounts_require_confirmation true
186
+
187
+ assert_emails 1 do
188
+ login
189
+ assert_redirected_to '/articles/secret'
190
+ follow_redirect!
191
+ assert_redirected_to '/confirm'
192
+ follow_redirect!
179
193
  end
180
- assert_equal QuoVadis::Log::ACCOUNT_CONFIRMATION, QuoVadis::Log.first.action
181
- assert_equal QuoVadis::Log::LOGIN_SUCCESS, log.action
194
+
195
+ assert_difference 'QuoVadis::Log.count' do
196
+ post quo_vadis.confirm_path(otp: extract_code_from_email)
197
+ end
198
+
199
+ assert_equal QuoVadis::Log::ACCOUNT_CONFIRMATION, log.action
200
+ ensure
201
+ QuoVadis.accounts_require_confirmation false
182
202
  end
183
203
 
184
204
 
@@ -241,4 +261,8 @@ class LoggingTest < IntegrationTest
241
261
  end
242
262
  end
243
263
 
264
+ def extract_code_from_email
265
+ ActionMailer::Base.deliveries.last.decoded[%r{\d{6}}, 0]
266
+ end
267
+
244
268
  end
@@ -105,7 +105,7 @@ class PasswordLoginTest < IntegrationTest
105
105
  travel 5.days
106
106
 
107
107
  get articles_path
108
- assert controller.logged_in? # flakey
108
+ assert controller.logged_in?
109
109
 
110
110
  travel 3.days
111
111