quo_vadis 2.1.6 → 2.1.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ae53bae73aaf968a8edec5148304394b411eace9c0df39069c6b240e90a6ea9
4
- data.tar.gz: 4980707b8a6670298f0d99f7f1a554767c12323055d5103057c3028837da49af
3
+ metadata.gz: 3776f82ebb4987586131a44692de5891ec821e7e82a1de249fc3d8905ef551d8
4
+ data.tar.gz: 6914f2e05d50ffbd9451d6e50484674d7bb775d2d871922749cc4be1553e35a6
5
5
  SHA512:
6
- metadata.gz: 00f025682cb8623ff02713e15bf25fdcc8c2a2964273bbc9f450f562375d18dd508ae1e729657cfa2275f2cb92672ef75b278edf8dbebd75cb2d6064d6c974b9
7
- data.tar.gz: c017aa443847c3d62dd17e8e384f50db13e11f63189008787be7d896ebc80a362318328139537859d8613a240607362ef2bd81c5022a99a358086f77d079bedf
6
+ metadata.gz: 5229c567fe09a76fad025b0dea0fc36192defd24e04077374816cfb99414b6a9cc3213e8410f5ee23fedb297d1b157d64345c317d646d1226655c5e60e50c1ac
7
+ data.tar.gz: 0ace7c8c5c1075eec8a54aa31ba01564a8a9ff0fec23387650a00a1721affca7c9326e083ac9f26a8afdd04bee6f6e8a748ae1c9bbf5b4e9d3b005804362afe6
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@
4
4
  ## HEAD
5
5
 
6
6
 
7
+ ## 2.1.9 (13 September 2022)
8
+
9
+ * Enable code to be run after sign up.
10
+
11
+
12
+ ## 2.1.8 (18 June 2022)
13
+
14
+ * Extract convenience method for has authentication account.
15
+ * Only authenticating models react to email change.
16
+
17
+
18
+ ## 2.1.7 (30 May 2022)
19
+
20
+ * Use SHA256 digest for encryption.
21
+ * Use <time> element in logs view.
22
+
23
+
7
24
  ## 2.1.6 (30 May 2022)
8
25
 
9
26
  * Fix typo in session scope.
@@ -23,7 +40,7 @@
23
40
 
24
41
  ## 2.1.3 (30 September 2021)
25
42
 
26
- * Pass IP and timestamp as paramenters to mailer.
43
+ * Pass IP and timestamp as parameters to mailer.
27
44
 
28
45
 
29
46
  ## 2.1.2 (30 September 2021)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Quo Vadis
2
2
 
3
- Multifactor authentication for your Rails 6 app.
3
+ Multifactor authentication for your Rails 6 or Rails 7 app.
4
4
 
5
5
  Designed in accordance with the [OWASP Application Security Verification Standard](https://owasp.org/www-project-application-security-verification-standard/) and relevant [OWASP Cheatsheets](https://cheatsheetseries.owasp.org).
6
6
 
@@ -177,7 +177,7 @@ Your new user sign-up form ([example](https://github.com/airblade/quo_vadis/blob
177
177
 
178
178
  In your controller, use the `#login` method to log in your new user. The optional second argument sets the length of the session (defaults to the browser session) - see the description above in the Controllers section).
179
179
 
180
- After logging in the user, redirect them to `qv.path_after_authentication` which resolves to a route named `:after_login`, if you have one, or your root route.
180
+ After logging in the user, redirect them wherever you like. You can use `qv.path_after_signup` which resolves to the first of these routes that exists: `:after_signup`, `:after_login`, the root route.
181
181
 
182
182
  ```ruby
183
183
  class UsersController < ApplicationController
@@ -185,7 +185,7 @@ class UsersController < ApplicationController
185
185
  @user = User.new user_params
186
186
  if @user.save
187
187
  login @user
188
- redirect_to qv.path_after_authentication
188
+ redirect_to qv.path_after_signup
189
189
  else
190
190
  # ...
191
191
  end
@@ -258,10 +258,7 @@ Next, write the page where the user can amend their email address if they made a
258
258
 
259
259
  Finally, write the page where people can put in their identifier (not their email, unless the identifier is email) again to request another confirmation email ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/confirmations/new.html.erb)). It must be in `app/views/quo_vadis/confirmations/new.html.:format`.
260
260
 
261
- After the user has confirmed their account, they will be logged in and redirected to the first of these that exists:
262
-
263
- - a route named `:after_login`;
264
- - your root route.
261
+ After the user has confirmed their account, they will be logged in and redirected to `qv.path_after_signup` which resolves to the first of these routes that exists: `:after_signup`, `:after_login`, the root route.
265
262
 
266
263
  So add whichever works best for you.
267
264
 
@@ -518,12 +515,13 @@ end
518
515
 
519
516
  __Routes__
520
517
 
521
- You can set up your post-authentication and post-password-change routes. If you don't, you must have a root route. For example:
518
+ You can set up your post-signup, post-authentication, and post-password-change routes. If you don't, you must have a root route. For example:
522
519
 
523
520
  ```ruby
524
521
  # config/routes.rb
525
- get '/dashboard', to: 'dashboards#show', as: 'after_login'
526
- get '/profile', to: 'profiles#show', as: 'after_password_change'
522
+ get '/signups/confirmed', to: 'dashboards#show', as: 'after_signup'
523
+ get '/dashboard', to: 'dashboards#show', as: 'after_login'
524
+ get '/profile', to: 'profiles#show', as: 'after_password_change'
527
525
  ```
528
526
 
529
527
  ### I18n
@@ -537,6 +535,6 @@ If you don't want a specific flash message at all, give the key an empty value i
537
535
 
538
536
  ## Intellectual Property
539
537
 
540
- Copyright 2011-2021 Andrew Stewart (boss@airbladesoftware.com).
538
+ Copyright 2011-2022 Andrew Stewart (boss@airbladesoftware.com).
541
539
 
542
540
  Released under the MIT licence.
@@ -51,7 +51,7 @@ module QuoVadis
51
51
  session.delete :account_pending_confirmation
52
52
 
53
53
  login account.model, true
54
- redirect_to qv.path_after_authentication, notice: QuoVadis.translate('flash.confirmation.confirmed')
54
+ redirect_to qv.path_after_signup, notice: QuoVadis.translate('flash.confirmation.confirmed')
55
55
  end
56
56
 
57
57
 
@@ -12,7 +12,7 @@
12
12
  <tbody>
13
13
  <% @logs.each do |log| %>
14
14
  <tr>
15
- <td><%= log.created_at %></td>
15
+ <td><time datetime="<%= log.created_at.to_formatted_s(:iso8601) %>"><%= log.created_at.to_formatted_s('%-d %B %Y') %></time></td>
16
16
  <td><%= QuoVadis.translate "log.action.#{log.action}" %></td>
17
17
  <td><%= log.ip %></td>
18
18
  <td><%= log.metadata.empty? ? '' : log.metadata.map {|k,v| "#{k}: #{v}"}.join(', ') %></td>
@@ -192,6 +192,13 @@ module QuoVadis
192
192
  Log.create account: account, action: action, ip: request.remote_ip, metadata: metadata
193
193
  end
194
194
 
195
+ def path_after_signup
196
+ return main_app.after_signup_path if main_app.respond_to?(:after_signup_path)
197
+ return main_app.after_login_path if main_app.respond_to?(:after_login_path)
198
+ return main_app.root_path if main_app.respond_to?(:root_path)
199
+ raise RuntimeError, 'Missing routes: after_signup_path, after_login_path, root_path; define at least one of them.'
200
+ end
201
+
195
202
  def path_after_authentication
196
203
  if (bookmark = rails_session[:qv_bookmark])
197
204
  rails_session.delete :qv_bookmark
@@ -8,7 +8,7 @@ module QuoVadis
8
8
  return '' if value == ''
9
9
 
10
10
  salt = SecureRandom.hex KEY_LENGTH
11
- crypt = encryptor key(salt)
11
+ crypt = encryptor salt
12
12
  ciphertext = crypt.encrypt_and_sign value
13
13
  [salt, ciphertext].join SEPARATOR
14
14
  end
@@ -18,7 +18,7 @@ module QuoVadis
18
18
  return '' if value == ''
19
19
 
20
20
  salt, data = value.split SEPARATOR
21
- crypt = encryptor key(salt)
21
+ crypt = encryptor salt
22
22
  crypt.decrypt_and_verify(data)
23
23
  end
24
24
 
@@ -27,12 +27,18 @@ module QuoVadis
27
27
  KEY_LENGTH = ActiveSupport::MessageEncryptor.key_len
28
28
  SEPARATOR = '$$'
29
29
 
30
- def self.encryptor(key)
31
- ActiveSupport::MessageEncryptor.new(key)
30
+ def self.encryptor(salt)
31
+ key_sha256 = key salt, OpenSSL::Digest::SHA256
32
+ key_sha1 = key salt, OpenSSL::Digest::SHA1
33
+ ActiveSupport::MessageEncryptor.new(key_sha256).tap { |crypt|
34
+ crypt.rotate key_sha1
35
+ }
32
36
  end
33
37
 
34
- def self.key(salt)
35
- ActiveSupport::KeyGenerator.new(secret).generate_key(salt, KEY_LENGTH)
38
+ def self.key(salt, hash_digest_class)
39
+ ActiveSupport::KeyGenerator
40
+ .new(secret, hash_digest_class: hash_digest_class)
41
+ .generate_key(salt, KEY_LENGTH)
36
42
  end
37
43
 
38
44
  def self.secret
@@ -14,7 +14,7 @@ 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_copy_identifier_to_account, if: Proc.new { |m| m.qv_account }
17
+ before_validation :qv_copy_identifier_to_account, if: :has_authentication_account?
18
18
 
19
19
  validate :qv_copy_password_errors, if: Proc.new { |m| m.qv_account&.password }
20
20
 
@@ -29,8 +29,8 @@ module QuoVadis
29
29
  qv_account.identifier = send identifier
30
30
  end
31
31
 
32
- after_update :qv_log_email_change, if: :saved_change_to_email?
33
- after_update :qv_notify_email_change, if: :saved_change_to_email?
32
+ after_update :qv_log_email_change, if: [:has_authentication_account?, :saved_change_to_email?]
33
+ after_update :qv_notify_email_change, if: [:has_authentication_account?, :saved_change_to_email?]
34
34
 
35
35
  QuoVadis.register_model self.name, identifier
36
36
  end
@@ -57,6 +57,10 @@ module QuoVadis
57
57
  qv_account.revoke
58
58
  end
59
59
 
60
+ def has_authentication_account?
61
+ !!qv_account
62
+ end
63
+
60
64
  private
61
65
 
62
66
  def qv_copy_password_errors
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- VERSION = '2.1.6'
4
+ VERSION = '2.1.9'
5
5
  end
@@ -26,6 +26,11 @@ class SignUpsController < ApplicationController
26
26
  @user = User.find params[:id]
27
27
  end
28
28
 
29
+ def confirmed
30
+ # Here we could send an email.
31
+ redirect_to after_login_path
32
+ end
33
+
29
34
  private
30
35
 
31
36
  def user_params
@@ -1,5 +1,6 @@
1
1
  Rails.application.routes.draw do
2
2
  resources :users
3
+ get '/sign_ups/confirmed', to: 'sign_ups#confirmed', as: 'after_signup'
3
4
  resources :sign_ups
4
5
  resources :articles do
5
6
  collection do
@@ -33,6 +33,8 @@ class AccountConfirmationTest < IntegrationTest
33
33
  put action_path
34
34
 
35
35
  # verify logged in
36
+ assert_redirected_to '/sign_ups/confirmed'
37
+ follow_redirect!
36
38
  assert_redirected_to '/articles/secret'
37
39
  assert_equal 'Thanks for confirming your account. You are now logged in.', flash[:notice]
38
40
  assert controller.logged_in?
@@ -101,6 +103,8 @@ class AccountConfirmationTest < IntegrationTest
101
103
 
102
104
  put extract_path_from_email
103
105
 
106
+ assert_redirected_to '/sign_ups/confirmed'
107
+ follow_redirect!
104
108
  assert_redirected_to '/articles/secret'
105
109
  assert_equal 'Thanks for confirming your account. You are now logged in.', flash[:notice]
106
110
 
@@ -54,7 +54,7 @@ class SessionsTest < IntegrationTest
54
54
  phone.get quo_vadis.sessions_path
55
55
  phone.assert_response :success
56
56
  phone.assert_select 'td', 'This session'
57
- phone.assert_select 'td input[type=submit][value="Log out"]', 1
57
+ phone.assert_select 'td button[type=submit]', text: 'Log out', count: 1
58
58
 
59
59
  # on phone, log out the desktop session
60
60
  phone.delete quo_vadis.session_path(QuoVadis::Session.first.id)
@@ -4,6 +4,13 @@ class CryptTest < ActiveSupport::TestCase
4
4
 
5
5
  setup do
6
6
  @crypt = QuoVadis::Crypt
7
+
8
+ @crypt_sha1 = Class.new(QuoVadis::Crypt) do
9
+ def self.encryptor(salt)
10
+ key_sha1 = key salt, OpenSSL::Digest::SHA1
11
+ ActiveSupport::MessageEncryptor.new key_sha1
12
+ end
13
+ end
7
14
  end
8
15
 
9
16
  test 'round trip' do
@@ -19,4 +26,16 @@ class CryptTest < ActiveSupport::TestCase
19
26
  refute_equal ciphertext, @crypt.encrypt(plaintext)
20
27
  end
21
28
 
29
+ test 'rotation' do
30
+ # This test only works if our test Rails contains this commit:
31
+ # https://github.com/rails/rails/commit/447e28347eb46e2ad5dc625de616152bd1b69a32
32
+ return unless ActiveSupport::KeyGenerator.respond_to? :hash_digest_class
33
+
34
+ plaintext = 'the quick brown fox'
35
+ # Encrypt with SHA1 digest
36
+ ciphertext_sha1 = @crypt_sha1.encrypt plaintext
37
+ # Ensure code can decrypt it.
38
+ assert_equal plaintext, @crypt.decrypt(ciphertext_sha1)
39
+ end
40
+
22
41
  end
@@ -66,4 +66,14 @@ class ModelTest < ActiveSupport::TestCase
66
66
  u.update email: 'robert@example.com'
67
67
  end
68
68
  end
69
+
70
+
71
+ test 'does not notify or log on email change when no account' do
72
+ u = User.create! name: 'bob', email: 'bob@example.com'
73
+ assert_no_difference 'QuoVadis::Log.count' do
74
+ assert_no_enqueued_emails do
75
+ u.update email: 'robert@example.com'
76
+ end
77
+ end
78
+ end
69
79
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quo_vadis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.6
4
+ version: 2.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Stewart
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-30 00:00:00.000000000 Z
11
+ date: 2022-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -224,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
224
  - !ruby/object:Gem::Version
225
225
  version: '0'
226
226
  requirements: []
227
- rubygems_version: 3.2.22
227
+ rubygems_version: 3.2.33
228
228
  signing_key:
229
229
  specification_version: 4
230
230
  summary: Multifactor authentication for Rails 6 and 7.