veri 2.0.1 → 2.0.3

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: 1d4ce50f8dee062c8b115095760ba5c2ef1e4f6a75db253a12fbb872583b5491
4
- data.tar.gz: 20948f1a5cfbfb8054b2fb66491d9babae554197487b1369f99f7b5e5d8a213c
3
+ metadata.gz: 90c72152f71aea2555de8f7788b356e8b957d94df73a0bfc826ee79b1087fed7
4
+ data.tar.gz: f552a61efc8b704a9065bca3d785dcb8cd4046299ee94e05dc0cb08b381511e4
5
5
  SHA512:
6
- metadata.gz: f49a78fc5ccba47ece10a67a2b651173ea28000a9aa43b886c514d74334c1273e59b77d07893590014b639b42f324636c025288f5e4a6657c932f222140fb258
7
- data.tar.gz: 8a3ab38f067b650708e4c0a13af58dbac9ac60fcca30b038c4fbd8a12c6851d55502a5f9033d886d9a345cbe7f06e34bced6160f4d812b8b9bef7792de707e75
6
+ metadata.gz: 1947901336055c81c82f36c18c6abdeadc76d6d1ce47564de69bd83cb98a75d14e433276df5be08c24506277571121d81eaf6c3c0805c5d1041f8d9272821d31
7
+ data.tar.gz: eb186110a8816965a3ae0c7f396fdd8b0fd212ce281602139268e55cf466e3ad7589e0838383840e629c2b6259ab027c1beb343b5f4338b778717a3b5670eaae
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## v2.0.3
2
+
3
+ ### Bugs
4
+
5
+ - Fixed authentication raising `NameError` when the configured user model is not named `User`
6
+ - Fixed Rails commands failing when the configured user model class does not exist yet
7
+ - Fixed expired and inactive sessions still appearing as logged in on pages that don't require authentication
8
+ - Fixed locking a user not terminating their sessions until their next request
9
+ - Fixed password verification raising an error for users who have no password set
10
+ - Fixed cross-tenant shapeshifting not working
11
+
12
+ ### Misc
13
+
14
+ - The hashing algorithm can now be changed without breaking existing passwords
15
+
16
+ ## v2.0.2
17
+
18
+ ### Misc
19
+
20
+ - Clarified error message for non-existent user model
21
+
1
22
  ## v2.0.1
2
23
 
3
24
  ### Misc
data/README.md CHANGED
@@ -70,13 +70,19 @@ Configure Veri in an initializer if customization is needed:
70
70
  ```rb
71
71
  # These are the default values; you can change them as needed
72
72
  Veri.configure do |config|
73
- config.hashing_algorithm = :argon2 # Password hashing algorithm (:argon2, :bcrypt, :pbkdf2, or :scrypt)
74
- config.inactive_session_lifetime = nil # Session inactivity timeout (nil means sessions never expire due to inactivity)
75
- config.total_session_lifetime = 14.days # Maximum session duration regardless of activity
76
- config.user_model_name = "User" # Your user model name
73
+ # Password hashing algorithm (:argon2, :bcrypt, :pbkdf2, or :scrypt)
74
+ config.hashing_algorithm = :argon2
75
+ # Session inactivity timeout (nil means sessions never expire due to inactivity)
76
+ config.inactive_session_lifetime = nil
77
+ # Maximum session duration regardless of activity
78
+ config.total_session_lifetime = 14.days
79
+ # Your user model name
80
+ config.user_model_name = "User"
77
81
  end
78
82
  ```
79
83
 
84
+ The hashing algorithm can be changed at any time: existing passwords are verified with the algorithm they were hashed with and are automatically re-hashed with the configured algorithm on the next successful login.
85
+
80
86
  ## Password Management
81
87
 
82
88
  Your user model is automatically extended with password management methods:
@@ -88,6 +94,12 @@ user.update_password("password")
88
94
  # Verify a password
89
95
  user.verify_password("password")
90
96
  ```
97
+ Changing a password does not automatically terminate existing sessions. If you want to invalidate the user's sessions after a password change, do so explicitly:
98
+
99
+ ```rb
100
+ user.update_password(new_password)
101
+ user.sessions.terminate_all
102
+ ```
91
103
 
92
104
  ## Controller Integration
93
105
 
@@ -99,11 +111,13 @@ Include the authentication module in your controllers and configure protection:
99
111
  class ApplicationController < ActionController::Base
100
112
  include Veri::Authentication
101
113
 
102
- with_authentication # Require authentication by default
114
+ # Require authentication by default
115
+ with_authentication
103
116
  end
104
117
 
105
118
  class PicturesController < ApplicationController
106
- skip_authentication only: [:index, :show] # Allow public access to index and show actions
119
+ # Allow public access to index and show actions
120
+ skip_authentication only: [:index, :show]
107
121
  end
108
122
  ```
109
123
 
@@ -161,7 +175,7 @@ current_user
161
175
  # Returns true if user is authenticated
162
176
  logged_in?
163
177
 
164
- # Authenticates user and creates session, returns true on success or false if account is locked
178
+ # Authenticates user, returns true on success or false if account is locked
165
179
  log_in(user)
166
180
 
167
181
  # Terminates current session
@@ -396,6 +410,8 @@ session.shapeshift(user, tenant: company)
396
410
  session.true_tenant
397
411
  ```
398
412
 
413
+ The `tenant:` argument always becomes the session's tenant: omitting it means no tenant. In a multi-tenant application, always pass it explicitly.
414
+
399
415
  All other session methods work the same way in multi-tenant applications as in single-tenant applications. However, `to_true_identity` will restore both the original user and tenant.
400
416
 
401
417
  ### Orphaned Sessions
@@ -408,6 +424,12 @@ To clean up orphaned sessions, use:
408
424
  Veri::Session.prune
409
425
  ```
410
426
 
427
+ Or, for a specific user:
428
+
429
+ ```rb
430
+ user.sessions.prune
431
+ ```
432
+
411
433
  ### Tenant Migrations
412
434
 
413
435
  When you rename or remove models used as tenants, you need to update Veri's stored data accordingly. Use these irreversible data migrations:
@@ -484,24 +506,26 @@ end
484
506
  ## Getting Help and Contributing
485
507
 
486
508
  ### Getting Help
487
- Have a question or need assistance? Open a discussion in our [discussions section](https://github.com/enjaku4/veri/discussions) for:
509
+
510
+ Have a question or need assistance? Open a discussion in the [discussions section](https://github.com/enjaku4/veri/discussions) for:
511
+
488
512
  - Usage questions
489
513
  - Implementation guidance
490
- - Feature suggestions
514
+ - Open-ended ideas or suggestions
515
+
516
+ ### Issues
517
+
518
+ [Issues](https://github.com/enjaku4/veri/issues) track bugs, planned features, and other work. When reporting a bug, please include:
491
519
 
492
- ### Reporting Issues
493
- Found a bug? Please [create an issue](https://github.com/enjaku4/veri/issues) with:
494
520
  - A clear description of the problem
495
521
  - Steps to reproduce the issue
496
- - Your environment details (Rails version, Ruby version, etc.)
522
+ - Your environment details (Rails and Ruby versions, OS, etc.)
497
523
 
498
524
  ### Contributing Code
499
- Ready to contribute? You can:
500
- - Fix bugs by submitting pull requests
501
- - Improve documentation
502
- - Add new features (please discuss first in our [discussions section](https://github.com/enjaku4/veri/discussions))
503
525
 
504
- Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/veri/blob/main/CONTRIBUTING.md)
526
+ Any open issue is free to pick up or discuss. Check the [project board](https://github.com/users/enjaku4/projects/13) or [issues tab](https://github.com/enjaku4/veri/issues).
527
+
528
+ Before contributing, please read the [contributing guidelines](https://github.com/enjaku4/veri/blob/main/CONTRIBUTING.md).
505
529
 
506
530
  ## License
507
531
 
@@ -513,6 +537,6 @@ Everyone interacting in the Veri project is expected to follow the [code of cond
513
537
 
514
538
  ## Old Versions
515
539
 
516
- Only the latest major version is supported. Older versions are obsolete and not maintained, but their READMEs are available here for reference:
540
+ Only the latest major version is actively maintained. READMEs for older versions are available here for reference:
517
541
 
518
542
  [v1.x.x](https://github.com/enjaku4/veri/blob/9c188e16a703141b7cd89dd31d5cd49a557f143d/README.md)
@@ -66,7 +66,7 @@ module Veri
66
66
  Veri::Inputs::Model.new(
67
67
  user_model_name,
68
68
  error: Veri::ConfigurationError,
69
- message: "Invalid user model name `#{user_model_name}`, expected an ActiveRecord model name as a string"
69
+ message: "Invalid user model name `#{user_model_name}`, model does not exist"
70
70
  ).process
71
71
  end
72
72
 
@@ -1,5 +1,3 @@
1
- require "zlib"
2
-
3
1
  module Veri
4
2
  module Authentication
5
3
  extend ActiveSupport::Concern
@@ -29,9 +27,9 @@ module Veri
29
27
  end
30
28
 
31
29
  def current_session
32
- token = cookies.encrypted["#{auth_cookie_prefix}_token"]
30
+ token = cookies.encrypted["veri_token"]
33
31
 
34
- @current_session ||= Session.lookup(token, resolved_tenant)
32
+ @current_session ||= Session.find_active(token, resolved_tenant)
35
33
  end
36
34
 
37
35
  def log_in(authenticatable)
@@ -44,13 +42,15 @@ module Veri
44
42
 
45
43
  token = Veri::Session.establish(processed_authenticatable, request, resolved_tenant)
46
44
 
47
- cookies.encrypted.permanent["#{auth_cookie_prefix}_token"] = { value: token, httponly: true }
45
+ cookies.encrypted.permanent["veri_token"] = { value: token, httponly: true, secure: request.ssl? }
46
+ reset_memoization
48
47
  true
49
48
  end
50
49
 
51
50
  def log_out
52
51
  current_session&.terminate
53
- cookies.delete("#{auth_cookie_prefix}_token")
52
+ cookies.delete("veri_token")
53
+ reset_memoization
54
54
  end
55
55
 
56
56
  def logged_in?
@@ -58,7 +58,7 @@ module Veri
58
58
  end
59
59
 
60
60
  def return_path
61
- cookies.signed["#{auth_cookie_prefix}_return_path"]
61
+ cookies.signed["veri_return_path"]
62
62
  end
63
63
 
64
64
  def shapeshifter?
@@ -68,20 +68,14 @@ module Veri
68
68
  private
69
69
 
70
70
  def with_authentication
71
- if logged_in? && current_session.active?
72
- if current_user.locked?
73
- log_out
74
- when_unauthenticated
75
- else
76
- current_session.update_info(request)
77
- end
78
-
71
+ if logged_in? && !current_user.locked?
72
+ current_session.update_info(request)
79
73
  return
80
74
  end
81
75
 
82
76
  log_out
83
77
 
84
- cookies.signed["#{auth_cookie_prefix}_return_path"] = { value: request.fullpath, expires: 15.minutes.from_now } if request.get? && request.format.html?
78
+ cookies.signed["veri_return_path"] = { value: request.fullpath, expires: 15.minutes.from_now } if request.get? && request.format.html?
85
79
 
86
80
  when_unauthenticated
87
81
  end
@@ -100,8 +94,8 @@ module Veri
100
94
  ).resolve
101
95
  end
102
96
 
103
- def auth_cookie_prefix
104
- @auth_cookie_prefix ||= "auth_#{Zlib.crc32(Marshal.dump(resolved_tenant))}"
97
+ def reset_memoization
98
+ @current_user = @current_session = nil
105
99
  end
106
100
  end
107
101
  end
@@ -23,14 +23,26 @@ module Veri
23
23
  end
24
24
 
25
25
  def verify_password(password)
26
- hasher.verify(
27
- Veri::Inputs::NonEmptyString.new(password, message: "Expected a non-empty string, got `#{password.inspect}`").process,
28
- hashed_password
29
- )
26
+ processed_password = Veri::Inputs::NonEmptyString.new(password, message: "Expected a non-empty string, got `#{password.inspect}`").process
27
+
28
+ return false if hashed_password.blank?
29
+
30
+ stored_hasher = Veri::Configuration::HASHERS.values.find { _1.match?(hashed_password) }
31
+
32
+ raise Veri::Error, "Unrecognized password hash format" unless stored_hasher
33
+
34
+ return false unless stored_hasher.verify(processed_password, hashed_password)
35
+
36
+ update_column(:hashed_password, hasher.create(processed_password)) unless stored_hasher == hasher
37
+
38
+ true
30
39
  end
31
40
 
32
41
  def lock!
33
- update!(locked: true, locked_at: Time.current)
42
+ transaction do
43
+ update!(locked: true, locked_at: Time.current)
44
+ sessions.terminate_all
45
+ end
34
46
  end
35
47
 
36
48
  def unlock!
@@ -5,8 +5,6 @@ module Veri
5
5
  class Session < ActiveRecord::Base
6
6
  self.table_name = "veri_sessions"
7
7
 
8
- belongs_to :authenticatable, class_name: Veri::Configuration.user_model_name
9
- belongs_to :original_authenticatable, class_name: Veri::Configuration.user_model_name, optional: true
10
8
  belongs_to :tenant, polymorphic: true, optional: true
11
9
  belongs_to :original_tenant, polymorphic: true, optional: true
12
10
 
@@ -128,10 +126,12 @@ module Veri
128
126
 
129
127
  alias terminate_all delete_all
130
128
 
131
- def lookup(token, resolved_tenant)
129
+ def find_active(token, resolved_tenant)
132
130
  return nil if token.blank?
133
131
 
134
- find_by(hashed_token: digest_token(token), **resolved_tenant)
132
+ session = find_by(hashed_token: digest_token(token), **resolved_tenant)
133
+
134
+ session&.active? ? session : nil
135
135
  end
136
136
 
137
137
  private
@@ -12,6 +12,10 @@ module Veri
12
12
  def verify(password, hashed_password)
13
13
  ::Argon2::Password.verify_password(password, hashed_password)
14
14
  end
15
+
16
+ def match?(hashed_password)
17
+ hashed_password.start_with?("$argon2")
18
+ end
15
19
  end
16
20
  end
17
21
  end
@@ -12,6 +12,10 @@ module Veri
12
12
  def verify(password, hashed_password)
13
13
  ::BCrypt::Password.new(hashed_password) == password
14
14
  end
15
+
16
+ def match?(hashed_password)
17
+ hashed_password.start_with?("$2")
18
+ end
15
19
  end
16
20
  end
17
21
  end
@@ -42,6 +42,10 @@ module Veri
42
42
 
43
43
  OpenSSL.fixed_length_secure_compare(recalculated_hash, hash)
44
44
  end
45
+
46
+ def match?(hashed_password)
47
+ hashed_password.start_with?("#{DIGEST}$")
48
+ end
45
49
  end
46
50
  end
47
51
  end
@@ -12,6 +12,10 @@ module Veri
12
12
  def verify(password, hashed_password)
13
13
  ::SCrypt::Password.new(hashed_password) == password
14
14
  end
15
+
16
+ def match?(hashed_password)
17
+ hashed_password.match?(/\A\h+\$\h+\$\h+\$/)
18
+ end
15
19
  end
16
20
  end
17
21
  end
data/lib/veri/railtie.rb CHANGED
@@ -22,8 +22,16 @@ module Veri
22
22
  end
23
23
  end
24
24
 
25
- user_model = Veri::Configuration.user_model
26
- user_model.include Veri::Authenticatable unless user_model < Veri::Authenticatable
25
+ begin
26
+ user_model = Veri::Configuration.user_model
27
+ user_model.include Veri::Authenticatable unless user_model < Veri::Authenticatable
28
+ rescue Veri::ConfigurationError
29
+ raise if Veri::Railtie.server_running?
30
+ end
31
+
32
+ Veri::Session.belongs_to :authenticatable, class_name: Veri::Configuration.user_model_name
33
+ Veri::Session.belongs_to :original_authenticatable, class_name: Veri::Configuration.user_model_name,
34
+ optional: true
27
35
  end
28
36
  end
29
37
 
data/lib/veri/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Veri
2
- VERSION = "2.0.1".freeze
2
+ VERSION = "2.0.3".freeze
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: veri
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - enjaku4
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-04 00:00:00.000000000 Z
10
+ date: 2026-07-19 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: argon2
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
147
  - !ruby/object:Gem::Version
148
148
  version: '0'
149
149
  requirements: []
150
- rubygems_version: 3.6.9
150
+ rubygems_version: 4.0.3
151
151
  specification_version: 4
152
152
  summary: Minimal cookie-based authentication library for Ruby on Rails
153
153
  test_files: []