rodauth 2.43.0 → 2.44.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebccee76adf022cf82eca6e36c69f5846353b4499021aec5f410dc7e3b04c65f
4
- data.tar.gz: b9fb907c3430c9b043c8cc361877c191ed8f4ed99388d410d0eb84b66cb57ab5
3
+ metadata.gz: 040bf69b63cc893df29a1a4e5c99889f5dc7fcae928c81639be5a38157dc910e
4
+ data.tar.gz: 0f013ee9f86f80379fe575251697fc622f9c0d9ede248e39df67565312edc6eb
5
5
  SHA512:
6
- metadata.gz: c38cbd45e2ef7a29c4de7c2e9450979a2739bae690af5dd34288684f8c6289423334033e26ecd96fca758cae0ef7f1a6c6af69de36d95651a6c577206721fde5
7
- data.tar.gz: 2c69a447d7d465e76b29f1746dc3f886881f39bb20af47d292093333104ab2e88edf74d4d6262f96f38492d69d284e23719e8cf29823b2a117fd52967c7b79b1
6
+ metadata.gz: a9d5ec9033e65cd7578dad8864eb5fcfb258f7ba4484e949923a3502e2e642a77898a01fa2dbb0947d92c4ce009e9b238d5e11a7243cf7f25ad566e9c38c6d72
7
+ data.tar.gz: 2fb3aafcb91f26560370059512bb074122bb87267c7d50c27aa465a2ede5b0380dec0d0d17cb946121ca83a28716ffad02427bc83ae9c4959c44f5bc82a21bb9
@@ -37,6 +37,8 @@ module Rodauth
37
37
  :remove_inactive_sessions,
38
38
  )
39
39
 
40
+ uses_instance_variables(:@active_sessions_key, :@clear_active_sessions_after_two_factor_setup)
41
+
40
42
  def currently_active_session?
41
43
  return false unless session_id = session[session_id_session_key]
42
44
 
@@ -148,6 +150,21 @@ module Rodauth
148
150
  remove_all_active_sessions
149
151
  end
150
152
 
153
+ def after_otp_setup
154
+ super if defined?(super)
155
+ remove_all_active_sessions_except_current if @clear_active_sessions_after_two_factor_setup
156
+ end
157
+
158
+ def after_sms_confirm
159
+ super if defined?(super)
160
+ remove_all_active_sessions_except_current if @clear_active_sessions_after_two_factor_setup
161
+ end
162
+
163
+ def after_webauthn_setup
164
+ super if defined?(super)
165
+ remove_all_active_sessions_except_current if @clear_active_sessions_after_two_factor_setup
166
+ end
167
+
151
168
  def before_logout
152
169
  if param_or_nil(global_logout_param)
153
170
  remove_remember_key(session_value) if respond_to?(:remove_remember_key)
@@ -158,6 +175,21 @@ module Rodauth
158
175
  super
159
176
  end
160
177
 
178
+ def before_otp_setup
179
+ @clear_active_sessions_after_two_factor_setup = !two_factor_authentication_setup?
180
+ super if defined?(super)
181
+ end
182
+
183
+ def before_sms_confirm
184
+ @clear_active_sessions_after_two_factor_setup = !two_factor_authentication_setup?
185
+ super if defined?(super)
186
+ end
187
+
188
+ def before_webauthn_setup
189
+ @clear_active_sessions_after_two_factor_setup = !two_factor_authentication_setup?
190
+ super if defined?(super)
191
+ end
192
+
161
193
  attr_reader :active_sessions_key
162
194
 
163
195
  def generate_active_sessions_key
@@ -16,6 +16,8 @@ module Rodauth
16
16
  auth_value_method :argon2_secret, nil
17
17
  auth_value_method :use_argon2?, true
18
18
 
19
+ uses_instance_variables(:@update_password_hash)
20
+
19
21
  def password_hash(password)
20
22
  return super unless use_argon2?
21
23
 
@@ -1,11 +1,24 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'rack/request'
4
- require 'rack/utils'
3
+ begin
4
+ require "rack/version"
5
+ # :nocov:
6
+ rescue LoadError
7
+ require "rack"
8
+ else
9
+ if Rack.release >= '3'
10
+ require "rack/request"
11
+ require "rack/utils"
12
+ else
13
+ require "rack"
14
+ end
15
+ end
16
+ # :nocov:
5
17
 
6
18
  module Rodauth
7
19
  Feature.define(:base, :Base) do
8
20
  after 'login'
21
+ after 'no_matching_login'
9
22
  after 'login_failure'
10
23
  before 'login'
11
24
  before 'login_attempt'
@@ -137,14 +150,29 @@ module Rodauth
137
150
  def auth_class_eval(&block)
138
151
  auth.class_eval(&block)
139
152
  end
153
+
154
+ def uses_instance_variables(*ivs)
155
+ auth.define_singleton_method(:instance_variables_used) do
156
+ super() + ivs
157
+ end
158
+ end
140
159
  end
141
160
 
161
+ uses_instance_variables(
162
+ :@account,
163
+ :@current_route,
164
+ :@field_errors,
165
+ :@password_field_autocomplete_value,
166
+ :@has_password
167
+ )
168
+
142
169
  attr_reader :scope
143
170
  attr_reader :account
144
171
  attr_reader :current_route
145
172
 
146
173
  def initialize(scope)
147
174
  @scope = scope
175
+ _initialize_instance_variables
148
176
  end
149
177
 
150
178
  def features
@@ -549,13 +577,16 @@ module Rodauth
549
577
  end
550
578
 
551
579
  def has_password?
552
- return @has_password if defined?(@has_password)
580
+ return @has_password unless @has_password.nil?
553
581
  return false unless account || session_value
554
582
  @has_password = !!get_password_hash
555
583
  end
556
584
 
557
585
  private
558
586
 
587
+ def _initialize_instance_variables
588
+ end
589
+
559
590
  def _around_rodauth
560
591
  yield
561
592
  end
@@ -16,6 +16,8 @@ module Rodauth
16
16
  :password_doesnt_match_previous_password?
17
17
  )
18
18
 
19
+ uses_instance_variables(:@dont_check_previous_password)
20
+
19
21
  def set_password(password)
20
22
  hash = super
21
23
  add_previous_password_hash(hash)
@@ -47,6 +47,8 @@ module Rodauth
47
47
 
48
48
  auth_private_methods :account_from_email_auth_key
49
49
 
50
+ uses_instance_variables(:@email_auth_key_value)
51
+
50
52
  internal_request_method
51
53
  internal_request_method :email_auth_request
52
54
  internal_request_method :valid_email_auth?
@@ -5,10 +5,12 @@ module Rodauth
5
5
  auth_value_method :http_basic_auth_realm, "protected"
6
6
  auth_value_method :require_http_basic_auth?, false
7
7
 
8
+ uses_instance_variables(:@checked_http_basic_auth)
9
+
8
10
  def logged_in?
9
11
  ret = super
10
12
 
11
- if !ret && !defined?(@checked_http_basic_auth)
13
+ if !ret && @checked_http_basic_auth.nil?
12
14
  http_basic_auth
13
15
  ret = super
14
16
  end
@@ -32,9 +34,11 @@ module Rodauth
32
34
  end
33
35
 
34
36
  def http_basic_auth
35
- return @checked_http_basic_auth if defined?(@checked_http_basic_auth)
37
+ unless @checked_http_basic_auth.nil?
38
+ return (@checked_http_basic_auth ? true : nil)
39
+ end
36
40
 
37
- @checked_http_basic_auth = nil
41
+ @checked_http_basic_auth = false
38
42
  return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
39
43
 
40
44
  username, password = token.unpack("m*").first.split(/:/, 2)
@@ -42,6 +46,7 @@ module Rodauth
42
46
 
43
47
  catch_error do
44
48
  unless account_from_login(username)
49
+ after_no_matching_login
45
50
  throw_basic_auth_error(login_param, no_matching_login_message)
46
51
  end
47
52
 
@@ -187,6 +187,7 @@ module Rodauth
187
187
  end
188
188
 
189
189
  def _set_internal_request_return_value(value)
190
+ @internal_request_return_value_set = true
190
191
  @internal_request_return_value = value
191
192
  end
192
193
 
@@ -219,7 +220,7 @@ module Rodauth
219
220
 
220
221
  def _handle_internal_request_eval(_)
221
222
  v = instance_eval(&internal_request_block)
222
- _set_internal_request_return_value(v) unless defined?(@internal_request_return_value)
223
+ _set_internal_request_return_value(v) unless @internal_request_return_value_set
223
224
  end
224
225
 
225
226
  def _handle_account_id_for_login(_)
@@ -304,6 +305,19 @@ module Rodauth
304
305
  end
305
306
 
306
307
  module InternalRequestClassMethods
308
+ def instance_variables_used
309
+ super + [
310
+ :@session,
311
+ :@params,
312
+ :@flash,
313
+ :@internal_request_block,
314
+ :@internal_request_return_value,
315
+ :@internal_request_return_value_set,
316
+ :@error_reason,
317
+ :@return_false_on_error
318
+ ]
319
+ end
320
+
307
321
  def internal_request(route, opts={}, &block)
308
322
  opts = opts.dup
309
323
 
@@ -404,6 +418,7 @@ module Rodauth
404
418
 
405
419
  internal_class.send(:extend, InternalRequestClassMethods)
406
420
  internal_class.send(:include, InternalRequestMethods)
421
+ internal_class.send(:make_shape_friendly)
407
422
  internal_class.allocate.post_configure
408
423
 
409
424
  ([:base] + internal_class.features).each do |feature_name|
@@ -27,6 +27,8 @@ module Rodauth
27
27
 
28
28
  auth_private_methods :json_response_body
29
29
 
30
+ uses_instance_variables(:@json_request)
31
+
30
32
  def set_field_error(field, message)
31
33
  return super unless use_json?
32
34
  json_response[json_response_field_error_key] = [field, message]
@@ -53,8 +55,8 @@ module Rodauth
53
55
  end
54
56
 
55
57
  def json_request?
56
- return @json_request if defined?(@json_request)
57
- @json_request = request.content_type =~ json_request_content_type_regexp
58
+ return @json_request unless @json_request.nil?
59
+ @json_request = !(request.content_type !~ json_request_content_type_regexp)
58
60
  end
59
61
 
60
62
  def use_json?
@@ -30,8 +30,10 @@ module Rodauth
30
30
 
31
31
  def_deprecated_alias :json_check_accept?, :jwt_check_accept?
32
32
 
33
+ uses_instance_variables(:@session, :@jwt_token, :@jwt_payload)
34
+
33
35
  def session
34
- return @session if defined?(@session)
36
+ return @session if @session
35
37
  return super unless use_jwt?
36
38
 
37
39
  s = {}
@@ -76,7 +78,7 @@ module Rodauth
76
78
  end
77
79
 
78
80
  def jwt_token
79
- return @jwt_token if defined?(@jwt_token)
81
+ return @jwt_token if @jwt_token
80
82
 
81
83
  if (v = request.env['HTTP_AUTHORIZATION']) && v !~ jwt_authorization_ignore
82
84
  @jwt_token = v.sub(jwt_authorization_remove, '')
@@ -120,7 +122,7 @@ module Rodauth
120
122
  end
121
123
 
122
124
  def jwt_payload
123
- return @jwt_payload if defined?(@jwt_payload)
125
+ return @jwt_payload unless @jwt_payload.nil?
124
126
  @jwt_payload = JWT.decode(jwt_token, _jwt_decode_secrets, true, _jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
125
127
  rescue JWT::DecodeError => e
126
128
  rescue_jwt_payload(e)
@@ -31,6 +31,8 @@ module Rodauth
31
31
  :account_from_refresh_token
32
32
  )
33
33
 
34
+ uses_instance_variables(:@jwt_refresh_route)
35
+
34
36
  route do |r|
35
37
  @jwt_refresh_route = true
36
38
  before_jwt_refresh_route
@@ -61,6 +61,8 @@ module Rodauth
61
61
  )
62
62
  auth_private_methods :account_from_unlock_key
63
63
 
64
+ uses_instance_variables(:@unlock_account_key_value)
65
+
64
66
  internal_request_method(:lock_account)
65
67
  internal_request_method(:unlock_account_request)
66
68
  internal_request_method(:unlock_account)
@@ -20,8 +20,8 @@ module Rodauth
20
20
 
21
21
  session_key :login_redirect_session_key, :login_redirect
22
22
 
23
- auth_cached_method :multi_phase_login_forms
24
- auth_cached_method :login_form_footer
23
+ cached_auth_method :multi_phase_login_forms
24
+ cached_auth_method :login_form_footer
25
25
 
26
26
  auth_value_methods :login_return_to_requested_location_path
27
27
 
@@ -30,6 +30,15 @@ module Rodauth
30
30
  :login_response
31
31
  )
32
32
 
33
+ uses_instance_variables(
34
+ :@login_form_footer,
35
+ :@login_form_footer_links,
36
+ :@login_form_header,
37
+ :@multi_phase_login_forms,
38
+ :@saved_login_redirect,
39
+ :@valid_login_entered
40
+ )
41
+
33
42
  internal_request_method
34
43
  internal_request_method :valid_login_and_password?
35
44
 
@@ -47,6 +56,7 @@ module Rodauth
47
56
 
48
57
  catch_error do
49
58
  unless account_from_login(login_param_value)
59
+ after_no_matching_login
50
60
  throw_error_reason(:no_matching_login, no_matching_login_error_status, login_param, no_matching_login_message)
51
61
  end
52
62
 
@@ -44,6 +44,8 @@ module Rodauth
44
44
  :set_password
45
45
  )
46
46
 
47
+ uses_instance_variables(:@password_requirement_message, :@login_requirement_message)
48
+
47
49
  def login_confirm_label
48
50
  "Confirm #{login_label}"
49
51
  end
@@ -68,8 +68,8 @@ module Rodauth
68
68
  auth_value_method :otp_setup_raw_param, 'otp_raw_secret'
69
69
  translatable_method :otp_auth_form_footer, ''
70
70
 
71
- auth_cached_method :otp_key
72
- auth_cached_method :otp
71
+ cached_auth_method :otp_key
72
+ cached_auth_method :otp
73
73
  private :otp
74
74
 
75
75
  auth_value_methods(
@@ -100,6 +100,8 @@ module Rodauth
100
100
  :otp_valid_code_for_old_secret
101
101
  )
102
102
 
103
+ uses_instance_variables(:@otp, :@otp_key, :@otp_user_key, :@otp_tmp_key)
104
+
103
105
  internal_request_method :otp_setup_params
104
106
  internal_request_method :otp_setup
105
107
  internal_request_method :otp_auth
@@ -245,7 +247,7 @@ module Rodauth
245
247
  end
246
248
 
247
249
  def otp_exists?
248
- !otp_key.nil?
250
+ !!otp_key
249
251
  end
250
252
 
251
253
  def otp_valid_code?(ot_pass)
@@ -277,7 +279,7 @@ module Rodauth
277
279
 
278
280
  def otp_remove
279
281
  otp_key_ds.delete
280
- @otp_key = nil
282
+ @otp_key = false
281
283
  end
282
284
 
283
285
  def otp_add_key
@@ -368,7 +370,7 @@ module Rodauth
368
370
  end
369
371
 
370
372
  def clear_cached_otp
371
- remove_instance_variable(:@otp) if defined?(@otp)
373
+ @otp = nil
372
374
  end
373
375
 
374
376
  def otp_tmp_key(secret)
@@ -436,7 +438,7 @@ module Rodauth
436
438
 
437
439
  def _otp_key
438
440
  @otp_user_key = nil
439
- otp_key_ds.get(otp_keys_column)
441
+ otp_key_ds.get(otp_keys_column) || false
440
442
  end
441
443
 
442
444
  def _otp_for_key(key)
@@ -56,6 +56,8 @@ module Rodauth
56
56
  :otp_unlock_refresh_tag,
57
57
  )
58
58
 
59
+ uses_instance_variables(:@otp_unlock_data)
60
+
59
61
  route(:otp_unlock) do |r|
60
62
  require_login
61
63
  require_account_session
@@ -7,6 +7,8 @@ module Rodauth
7
7
 
8
8
  auth_methods :password_recently_entered?
9
9
 
10
+ uses_instance_variables(:@last_password_entry)
11
+
10
12
  def modifications_require_password?
11
13
  return false unless super
12
14
  !password_recently_entered?
@@ -26,7 +28,7 @@ module Rodauth
26
28
 
27
29
  def update_session
28
30
  super
29
- set_session_value(last_password_entry_session_key, @last_password_entry) if defined?(@last_password_entry)
31
+ set_session_value(last_password_entry_session_key, @last_password_entry) if @last_password_entry
30
32
  end
31
33
 
32
34
  private
@@ -8,6 +8,8 @@ module Rodauth
8
8
  auth_value_method :previous_password_peppers, [""]
9
9
  auth_value_method :password_pepper_update?, true
10
10
 
11
+ uses_instance_variables(:@previous_pepper_matched)
12
+
11
13
  def password_match?(password)
12
14
  if (result = super) && @previous_pepper_matched && password_pepper_update?
13
15
  set_password(password)
@@ -46,7 +46,7 @@ module Rodauth
46
46
  translatable_method :recovery_auth_link_text, "Authenticate Using Recovery Code"
47
47
  translatable_method :recovery_codes_link_text, "View Authentication Recovery Codes"
48
48
 
49
- auth_cached_method :recovery_codes
49
+ cached_auth_method :recovery_codes
50
50
 
51
51
  auth_value_methods(
52
52
  :recovery_codes_primary?
@@ -60,6 +60,8 @@ module Rodauth
60
60
  :recovery_codes_available?,
61
61
  )
62
62
 
63
+ uses_instance_variables(:@recovery_codes, :@recovery_codes_button)
64
+
63
65
  internal_request_method :recovery_codes
64
66
  internal_request_method :recovery_auth
65
67
  internal_request_method :valid_recovery_auth?
@@ -181,7 +183,7 @@ module Rodauth
181
183
  add_recovery_code
182
184
  end
183
185
  end
184
- remove_instance_variable(:@recovery_codes)
186
+ @recovery_codes = nil
185
187
  end
186
188
 
187
189
  def add_recovery_code
@@ -49,6 +49,8 @@ module Rodauth
49
49
  :remove_remember_key
50
50
  )
51
51
 
52
+ uses_instance_variables(:@remember_key_value)
53
+
52
54
  internal_request_method :remember_setup
53
55
  internal_request_method :remember_disable
54
56
  internal_request_method :account_id_for_remember_key
@@ -57,6 +57,8 @@ module Rodauth
57
57
  :account_from_reset_password_key
58
58
  )
59
59
 
60
+ uses_instance_variables(:@reset_password_key_value)
61
+
60
62
  internal_request_method(:reset_password_request)
61
63
  internal_request_method
62
64
 
@@ -89,8 +89,6 @@ module Rodauth
89
89
  auth_value_method :sms_phone_min_length, 7
90
90
  auth_value_method :sms_phone_param, 'sms-phone'
91
91
 
92
- auth_cached_method :sms
93
-
94
92
  auth_value_methods(
95
93
  :sms_codes_primary?,
96
94
  :sms_needs_confirmation_notice_flash,
@@ -98,6 +96,7 @@ module Rodauth
98
96
  )
99
97
 
100
98
  auth_methods(
99
+ :sms,
101
100
  :sms_auth_message,
102
101
  :sms_available?,
103
102
  :sms_code_issued_at,
@@ -122,6 +121,8 @@ module Rodauth
122
121
  :sms_valid_phone?
123
122
  )
124
123
 
124
+ uses_instance_variables(:@sms)
125
+
125
126
  internal_request_method :sms_setup
126
127
  internal_request_method :sms_confirm
127
128
  internal_request_method :sms_request
@@ -356,7 +357,7 @@ module Rodauth
356
357
 
357
358
  def sms_disable
358
359
  sms_ds.delete
359
- @sms = nil
360
+ @sms = false
360
361
  end
361
362
 
362
363
  def sms_confirm_failure
@@ -367,7 +368,7 @@ module Rodauth
367
368
  # Cannot handle uniqueness violation here, as the phone number given may not match the
368
369
  # one in the table.
369
370
  sms_ds.insert(sms_id_column=>session_value, sms_phone_column=>phone_number, sms_failures_column => nil)
370
- remove_instance_variable(:@sms) if instance_variable_defined?(:@sms)
371
+ @sms = nil
371
372
  end
372
373
 
373
374
  def sms_remove_failures
@@ -521,8 +522,15 @@ module Rodauth
521
522
  update_hash_ds(sms, sms_ds, values)
522
523
  end
523
524
 
524
- def _sms
525
- sms_ds.first
525
+ def sms
526
+ case @sms
527
+ when nil
528
+ (@sms = sms_ds.first || false) || nil
529
+ when false
530
+ nil
531
+ else
532
+ @sms
533
+ end
526
534
  end
527
535
 
528
536
  def sms_ds
@@ -62,6 +62,8 @@ module Rodauth
62
62
  :two_factor_remove_links
63
63
  )
64
64
 
65
+ uses_instance_variables(:@two_factor_auth_links, :@two_factor_setup_links, :@two_factor_remove_links)
66
+
65
67
  internal_request_method :two_factor_disable
66
68
 
67
69
  route(:two_factor_manage, 'multifactor-manage') do |r|
@@ -4,6 +4,8 @@ module Rodauth
4
4
  Feature.define(:update_password_hash, :UpdatePasswordHash) do
5
5
  depends :login_password_requirements_base
6
6
 
7
+ uses_instance_variables(:@current_password_hash_cost, :@update_password_hash)
8
+
7
9
  def password_match?(password)
8
10
  if (result = super) && update_password_hash?
9
11
  @update_password_hash = false
@@ -59,6 +59,8 @@ module Rodauth
59
59
  :account_from_verify_account_key
60
60
  )
61
61
 
62
+ uses_instance_variables(:@verify_account_key_value)
63
+
62
64
  internal_request_method(:verify_account_resend)
63
65
  internal_request_method
64
66
 
@@ -51,6 +51,8 @@ module Rodauth
51
51
  :account_from_verify_login_change_key
52
52
  )
53
53
 
54
+ uses_instance_variables(:@verify_login_change_key_value, :@verify_login_change_new_login)
55
+
54
56
  internal_request_method
55
57
 
56
58
  route do |r|
@@ -12,6 +12,8 @@ module Rodauth
12
12
 
13
13
  auth_value_method :webauthn_login_user_verification_additional_factor?, false
14
14
 
15
+ uses_instance_variables(:@webauthn_login)
16
+
15
17
  internal_request_method :webauthn_login_params
16
18
  internal_request_method :webauthn_login
17
19
 
@@ -4,6 +4,8 @@ module Rodauth
4
4
  Feature.define(:webauthn_verify_account, :WebauthnVerifyAccount) do
5
5
  depends :verify_account, :webauthn
6
6
 
7
+ uses_instance_variables(:@webauthn_credential)
8
+
7
9
  def verify_account_view
8
10
  webauthn_setup_view
9
11
  end
@@ -6,7 +6,7 @@ module Rodauth
6
6
  MAJOR = 2
7
7
 
8
8
  # The minor version of Rodauth, updated for new feature releases of Rodauth.
9
- MINOR = 43
9
+ MINOR = 44
10
10
 
11
11
  # The patch version of Rodauth, updated only for bug fixes from the last
12
12
  # feature release.
data/lib/rodauth.rb CHANGED
@@ -63,6 +63,7 @@ module Rodauth
63
63
  end
64
64
  auth_class.class_eval{@configuration_name = opts[:name] unless defined?(@configuration_name)}
65
65
  auth_class.configure(&block) if block
66
+ auth_class.send(:make_shape_friendly)
66
67
  auth_class.allocate.post_configure if auth_class.method_defined?(:post_configure)
67
68
  end
68
69
 
@@ -125,6 +126,7 @@ module Rodauth
125
126
  attr_accessor :routes
126
127
  attr_accessor :configuration
127
128
  attr_reader :internal_request_methods
129
+ attr_reader :instance_variables_used
128
130
 
129
131
  def route(name=feature_name, default=name.to_s.tr('_', '-'), &block)
130
132
  route_meth = :"#{name}_route"
@@ -175,6 +177,10 @@ module Rodauth
175
177
  (@internal_request_methods ||= []) << name
176
178
  end
177
179
 
180
+ def uses_instance_variables(*ivs)
181
+ @instance_variables_used = ivs.freeze
182
+ end
183
+
178
184
  def configuration_module_eval(&block)
179
185
  configuration.module_eval(&block)
180
186
  end
@@ -305,7 +311,24 @@ module Rodauth
305
311
  auth_value_methods(meth)
306
312
  end
307
313
 
308
- def auth_cached_method(meth, iv=:"@#{meth}")
314
+ # Auth caching method that treats a nil instance variable as
315
+ # not being cached. If nil is a valid value for the instance
316
+ # variable, do not use this method, use a regular auth_method
317
+ # and handle caching manually.
318
+ def cached_auth_method(meth, iv=:"@#{meth}")
319
+ umeth = :"_#{meth}"
320
+ define_method(meth) do
321
+ v = instance_variable_get(iv)
322
+ v.nil? ? instance_variable_set(iv, send(umeth)) : v
323
+ end
324
+ alias_method(meth, meth)
325
+ auth_private_methods(meth)
326
+ end
327
+
328
+ # :nocov:
329
+ def auth_cached_method(meth, iv=:"@#{meth}") # :nodoc:
330
+ # Non-shape friendly historical method.
331
+ # RODAUTH3: Remove
309
332
  umeth = :"_#{meth}"
310
333
  define_method(meth) do
311
334
  if instance_variable_defined?(iv)
@@ -317,6 +340,7 @@ module Rodauth
317
340
  alias_method(meth, meth)
318
341
  auth_private_methods(meth)
319
342
  end
343
+ # :nocov:
320
344
 
321
345
  [:notice_flash, :error_flash, :button].each do |meth|
322
346
  define_method(meth) do |v, name=feature_name|
@@ -376,6 +400,60 @@ module Rodauth
376
400
  attr_accessor :route_hash
377
401
  attr_reader :configuration_name
378
402
  attr_reader :configuration
403
+
404
+ private
405
+
406
+ if RUBY_VERSION >= "3.2" && defined?(RubyVM::YJIT.enable)
407
+ # Use a shape-friendly object on Ruby 3.2 if YJIT is available
408
+ # with the assumption that it will be enabled later in any situation
409
+ # desiring high performance.
410
+ def make_shape_friendly
411
+ ivs = instance_variables_used
412
+
413
+ unless ivs.empty?
414
+ ivs.uniq!
415
+ ivs = ivs.reverse.join(" = ")
416
+ method_content = "#{ivs} = nil"
417
+ class_eval(<<-RUBY, __FILE__, __LINE__+1)
418
+ private def _initialize_instance_variables
419
+ #{method_content}
420
+ end
421
+ alias _initialize_instance_variables _initialize_instance_variables
422
+ RUBY
423
+ end
424
+
425
+ nil
426
+ end
427
+
428
+ def instance_variables_used
429
+ ivs = []
430
+ features = []
431
+
432
+ # Try to ensure that instance variable order follows feature enablement order
433
+ ancestors.each do |mod|
434
+ features << mod if mod.is_a?(Feature)
435
+ end
436
+
437
+ features.reverse_each do |mod|
438
+ if (feature_ivs = mod.instance_variables_used)
439
+ feature_ivs.each do |iv|
440
+ unless iv.match?(/\A@[a-z_][a-z0-9_]*\z/)
441
+ raise ConfigurationError, "invalid model instance variable used"
442
+ end
443
+
444
+ ivs << iv
445
+ end
446
+ end
447
+ end
448
+
449
+ ivs
450
+ end
451
+ # :nocov:
452
+ else
453
+ def make_shape_friendly # :nodoc:
454
+ end
455
+ end
456
+ # :nocov:
379
457
  end
380
458
 
381
459
  def self.inherited(subclass)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.43.0
4
+ version: 2.44.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -403,7 +403,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
403
403
  - !ruby/object:Gem::Version
404
404
  version: '0'
405
405
  requirements: []
406
- rubygems_version: 4.0.3
406
+ rubygems_version: 4.0.10
407
407
  specification_version: 4
408
408
  summary: Authentication and Account Management Framework for Rack Applications
409
409
  test_files: []