rodauth 2.37.0 → 2.39.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: 8c4f8a9edcebe8714dfa15132986c74e12e1823837ab71230008c42df9c2432d
4
- data.tar.gz: f5cb984675323f2c2b83bd1d7be0626f511381b69e090c1d39bfb7b31d09321d
3
+ metadata.gz: 3b8c2b404f5a8fad1607ba2bfd64ec4e03579a909ea324a0c9cda7705d9e79f7
4
+ data.tar.gz: bb2e8dcf2c4afa1d855379b69845c6d5efff8e097b5e95b38a11948c8d381d5d
5
5
  SHA512:
6
- metadata.gz: 3cdaaafebe4a7dba8b985dd1fbf39087a95fb3a49150e67487024e9374c50da6eced618ef702bfb70fbc483f83f9c29dba873d07bac2b4c1ec442007f3556f61
7
- data.tar.gz: 8774b5ae4c7e430f76705857bd0e80a59a0171c60b9693581ee7eae7c07a1c300b89c38c7d7f660cf01e48e6c2190597449eec4cadf57cd38c08cbe8a2783886
6
+ metadata.gz: 4316cb199e760aaa144e7c1b154f974f514abdea83b5a18d4f85dd01ff64a9d8c030fd028cee8c27c00d3402a951571b3ae1379ca7ef8653c991df47035ef5c7
7
+ data.tar.gz: 0f1fa083577edf23a8db579ab5df5c9ef0d6087c97a7d2cea2c1ef7071049e495d3f374f5657194db4d6feef2252bb708f85af3a1a66e7f116185406db488984
@@ -67,6 +67,7 @@ module Rodauth
67
67
  auth_value_method :unopen_account_error_status, 403
68
68
  translatable_method :unverified_account_message, "unverified account, please verify account before logging in"
69
69
  auth_value_method :default_field_attributes, ''
70
+ auth_value_method :use_template_fixed_locals?, true
70
71
 
71
72
  redirect(:require_login){"#{prefix}/login"}
72
73
 
@@ -409,6 +410,7 @@ module Rodauth
409
410
 
410
411
  def button_opts(value, opts)
411
412
  opts = Hash[template_opts].merge!(opts)
413
+ _merge_fixed_locals_opts(opts, button_fixed_locals)
412
414
  opts[:locals] = {:value=>value, :opts=>opts}
413
415
  opts[:cache] = cache_templates
414
416
  opts[:cache_key] = :rodauth_button
@@ -542,6 +544,12 @@ module Rodauth
542
544
  has_password? ? ['password'] : []
543
545
  end
544
546
 
547
+ def has_password?
548
+ return @has_password if defined?(@has_password)
549
+ return false unless account || session_value
550
+ @has_password = !!get_password_hash
551
+ end
552
+
545
553
  private
546
554
 
547
555
  def _around_rodauth
@@ -555,6 +563,56 @@ module Rodauth
555
563
  s
556
564
  end
557
565
 
566
+ if Rack.release >= '3'
567
+ def set_response_header(key, value)
568
+ response.headers[key] = value
569
+ end
570
+
571
+ def convert_response_header_key(key)
572
+ key
573
+ end
574
+ # :nocov:
575
+ else
576
+ def set_response_header(key, value)
577
+ response.headers[convert_response_header_key(key)] = value
578
+ end
579
+
580
+ # Attempt backwards compatibility on Rack < 3 by changing
581
+ # known cases from lower case to mixed case.
582
+ mixed_case_headers = {}
583
+ (<<-END).split.each { |k| mixed_case_headers[k.downcase.freeze] = k.freeze }
584
+ Access-Control-Allow-Headers
585
+ Access-Control-Allow-Methods
586
+ Access-Control-Allow-Origin
587
+ Access-Control-Expose-Headers
588
+ Access-Control-Max-Age
589
+ Allow
590
+ Authorization
591
+ Content-Type
592
+ Content-Length
593
+ WWW-Authenticate
594
+ END
595
+ mixed_case_headers.freeze
596
+ define_method(:convert_response_header_key) do |key|
597
+ mixed_case_headers.fetch(key, key)
598
+ end
599
+ end
600
+ # :nocov:
601
+
602
+ if RUBY_VERSION >= '2.1'
603
+ def button_fixed_locals
604
+ '(value:, opts:)'
605
+ end
606
+ # :nocov:
607
+ else
608
+ # Work on Ruby 2.0 when using Tilt 2.6+, as Ruby 2.0 does
609
+ # not support required keyword arguments.
610
+ def button_fixed_locals
611
+ '(value: nil, opts: nil)'
612
+ end
613
+ end
614
+ # :nocov:
615
+
558
616
  def database_function_password_match?(name, hash_id, password, salt)
559
617
  db.get(Sequel.function(function_name(name), hash_id, password_hash_using_salt(password, salt)))
560
618
  end
@@ -718,12 +776,6 @@ module Rodauth
718
776
  end
719
777
  end
720
778
 
721
- def has_password?
722
- return @has_password if defined?(@has_password)
723
- return false unless account || session_value
724
- @has_password = !!get_password_hash
725
- end
726
-
727
779
  def password_hash_using_salt(password, salt)
728
780
  BCrypt::Engine.hash_secret(password, salt)
729
781
  end
@@ -766,7 +818,7 @@ module Rodauth
766
818
  end
767
819
 
768
820
  def compute_raw_hmac(data)
769
- raise ArgumentError, "hmac_secret not set" unless hmac_secret
821
+ raise ConfigurationError, "hmac_secret not set" unless hmac_secret
770
822
  compute_raw_hmac_with_secret(data, hmac_secret)
771
823
  end
772
824
 
@@ -885,7 +937,7 @@ module Rodauth
885
937
 
886
938
  def require_response(meth)
887
939
  send(meth)
888
- raise RuntimeError, "#{meth.to_s.sub(/\A_/, '')} overridden without returning a response (should use redirect or request.halt). This is a bug in your Rodauth configuration, not a bug in Rodauth itself."
940
+ raise ConfigurationError, "#{meth.to_s.sub(/\A_/, '')} overridden without returning a response (should use redirect or request.halt)."
889
941
  end
890
942
 
891
943
  def set_session_value(key, value)
@@ -912,6 +964,7 @@ module Rodauth
912
964
 
913
965
  def _view_opts(page)
914
966
  opts = template_opts.dup
967
+ _merge_fixed_locals_opts(opts, '(rodauth: self.rodauth)')
915
968
  opts[:locals] = opts[:locals] ? opts[:locals].dup : {}
916
969
  opts[:locals][:rodauth] = self
917
970
  opts[:cache] = cache_templates
@@ -919,6 +972,14 @@ module Rodauth
919
972
  _template_opts(opts, page)
920
973
  end
921
974
 
975
+ def _merge_fixed_locals_opts(opts, fixed_locals)
976
+ if use_template_fixed_locals? && !opts[:locals]
977
+ fixed_locals_opts = {default_fixed_locals: fixed_locals}
978
+ fixed_locals_opts.merge!(opts[:template_opts]) if opts[:template_opts]
979
+ opts[:template_opts] = fixed_locals_opts
980
+ end
981
+ end
982
+
922
983
  # Set the template path only if there isn't an overridden template in the application.
923
984
  # Result should replace existing template opts.
924
985
  def _template_opts(opts, page)
@@ -930,6 +991,10 @@ module Rodauth
930
991
  end
931
992
 
932
993
  def _view(meth, page)
994
+ unless scope.respond_to?(meth)
995
+ raise ConfigurationError, "attempted to render a built-in view/email template (#{page.inspect}), but rendering is disabled"
996
+ end
997
+
933
998
  scope.send(meth, _view_opts(page))
934
999
  end
935
1000
  end
@@ -163,6 +163,10 @@ module Rodauth
163
163
  methods
164
164
  end
165
165
 
166
+ def email_auth_email_recently_sent?
167
+ (email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
168
+ end
169
+
166
170
  private
167
171
 
168
172
  def _multi_phase_login_forms
@@ -171,10 +175,6 @@ module Rodauth
171
175
  forms
172
176
  end
173
177
 
174
- def email_auth_email_recently_sent?
175
- (email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
176
- end
177
-
178
178
  def _email_auth_request
179
179
  if email_auth_email_recently_sent?
180
180
  set_redirect_error_flash email_auth_email_recently_sent_error_flash
@@ -73,7 +73,7 @@ module Rodauth
73
73
 
74
74
  def set_http_basic_auth_error_response
75
75
  response.status = 401
76
- response.headers["WWW-Authenticate"] = "Basic realm=\"#{http_basic_auth_realm}\""
76
+ set_response_header("www-authenticate", "Basic realm=\"#{http_basic_auth_realm}\"")
77
77
  end
78
78
 
79
79
  def throw_basic_auth_error(*args)
@@ -186,7 +186,7 @@ module Rodauth
186
186
 
187
187
  unless request.post?
188
188
  response.status = 405
189
- response.headers['Allow'] = 'POST'
189
+ set_response_header('allow', 'POST')
190
190
  json_response[json_response_error_key] = json_non_post_error_message
191
191
  return_json_response
192
192
  end
@@ -209,7 +209,7 @@ module Rodauth
209
209
 
210
210
  def _return_json_response
211
211
  response.status ||= json_response_error_status if json_response_error?
212
- response['Content-Type'] ||= json_response_content_type
212
+ response.headers[convert_response_header_key('content-type')] ||= json_response_content_type
213
213
  return_response _json_response_body(json_response)
214
214
  end
215
215
 
@@ -47,7 +47,7 @@ module Rodauth
47
47
 
48
48
  if session_data
49
49
  if jwt_symbolize_deeply?
50
- s = JSON.parse(JSON.fast_generate(session_data), :symbolize_names=>true)
50
+ s = JSON.parse(JSON.generate(session_data), :symbolize_names=>true)
51
51
  elsif scope.opts[:sessions_convert_symbols]
52
52
  s = session_data
53
53
  else
@@ -64,7 +64,7 @@ module Rodauth
64
64
  end
65
65
 
66
66
  def jwt_secret
67
- raise ArgumentError, "jwt_secret not set"
67
+ raise ConfigurationError, "jwt_secret not set"
68
68
  end
69
69
 
70
70
  def jwt_session_hash
@@ -84,7 +84,7 @@ module Rodauth
84
84
  end
85
85
 
86
86
  def set_jwt_token(token)
87
- response.headers['Authorization'] = token
87
+ set_response_header('authorization', token)
88
88
  end
89
89
 
90
90
  def use_jwt?
@@ -105,7 +105,7 @@ module Rodauth
105
105
  jwt_decode_opts
106
106
  end
107
107
 
108
- if JWT::VERSION::MAJOR > 2 || (JWT::VERSION::MAJOR == 2 && JWT::VERSION::MINOR >= 4)
108
+ if JWT.gem_version >= Gem::Version.new("2.4")
109
109
  def _jwt_decode_secrets
110
110
  secrets = [jwt_secret, jwt_old_secret]
111
111
  secrets.compact!
@@ -33,18 +33,18 @@ module Rodauth
33
33
 
34
34
  def before_rodauth
35
35
  if jwt_cors_allow?
36
- response['Access-Control-Allow-Origin'] = request.env['HTTP_ORIGIN']
36
+ set_response_header('access-control-allow-origin', request.env['HTTP_ORIGIN'])
37
37
 
38
38
  # Handle CORS preflight request
39
39
  if request.request_method == 'OPTIONS'
40
- response['Access-Control-Allow-Methods'] = jwt_cors_allow_methods
41
- response['Access-Control-Allow-Headers'] = jwt_cors_allow_headers
42
- response['Access-Control-Max-Age'] = jwt_cors_max_age.to_s
40
+ set_response_header('access-control-allow-methods', jwt_cors_allow_methods)
41
+ set_response_header('access-control-allow-headers', jwt_cors_allow_headers)
42
+ set_response_header('access-control-max-age', jwt_cors_max_age.to_s)
43
43
  response.status = 204
44
44
  return_response
45
45
  end
46
46
 
47
- response['Access-Control-Expose-Headers'] = jwt_cors_expose_headers
47
+ set_response_header('access-control-expose-headers', jwt_cors_expose_headers)
48
48
  end
49
49
 
50
50
  super
@@ -237,6 +237,10 @@ module Rodauth
237
237
  account_lockouts_ds.update(account_lockouts_email_last_sent_column=>Sequel::CURRENT_TIMESTAMP) if account_lockouts_email_last_sent_column
238
238
  end
239
239
 
240
+ def unlock_account_email_recently_sent?
241
+ (email_last_sent = get_unlock_account_email_last_sent) && (Time.now - email_last_sent < unlock_account_skip_resend_email_within)
242
+ end
243
+
240
244
  private
241
245
 
242
246
  attr_reader :unlock_account_key_value
@@ -278,10 +282,6 @@ module Rodauth
278
282
  return_response unlock_account_request_view
279
283
  end
280
284
 
281
- def unlock_account_email_recently_sent?
282
- (email_last_sent = get_unlock_account_email_last_sent) && (Time.now - email_last_sent < unlock_account_skip_resend_email_within)
283
- end
284
-
285
285
  def use_date_arithmetic?
286
286
  super || db.database_type == :mysql
287
287
  end
@@ -15,6 +15,7 @@ module Rodauth
15
15
  auth_value_method :login_error_status, 401
16
16
  translatable_method :login_form_footer_links_heading, '<h2 class="rodauth-login-form-footer-links-heading">Other Options</h2>'
17
17
  auth_value_method :login_return_to_requested_location?, false
18
+ auth_value_method :login_return_to_requested_location_max_path_size, 2048
18
19
  auth_value_method :use_multi_phase_login?, false
19
20
 
20
21
  session_key :login_redirect_session_key, :login_redirect
@@ -95,7 +96,7 @@ module Rodauth
95
96
  end
96
97
 
97
98
  def login_required
98
- if login_return_to_requested_location? && (path = login_return_to_requested_location_path)
99
+ if login_return_to_requested_location? && (path = login_return_to_requested_location_path) && path.bytesize <= login_return_to_requested_location_max_path_size
99
100
  set_session_value(login_redirect_session_key, path)
100
101
  end
101
102
  super
@@ -204,16 +204,16 @@ module Rodauth
204
204
  end
205
205
  end
206
206
 
207
+ def reset_password_email_recently_sent?
208
+ (email_last_sent = get_reset_password_email_last_sent) && (Time.now - email_last_sent < reset_password_skip_resend_email_within)
209
+ end
210
+
207
211
  private
208
212
 
209
213
  def _login_form_footer_links
210
214
  super << [20, reset_password_request_path, reset_password_request_link_text]
211
215
  end
212
216
 
213
- def reset_password_email_recently_sent?
214
- (email_last_sent = get_reset_password_email_last_sent) && (Time.now - email_last_sent < reset_password_skip_resend_email_within)
215
- end
216
-
217
217
  attr_reader :reset_password_key_value
218
218
 
219
219
  def after_login_failure
@@ -514,7 +514,7 @@ module Rodauth
514
514
  end
515
515
 
516
516
  def sms_send(phone, message)
517
- raise NotImplementedError, "sms_send needs to be defined in the Rodauth configuration for SMS sending to work"
517
+ raise ConfigurationError, "sms_send needs to be defined in the Rodauth configuration for SMS sending to work"
518
518
  end
519
519
 
520
520
  def update_sms(values)
@@ -240,6 +240,10 @@ module Rodauth
240
240
  send_verify_account_email
241
241
  end
242
242
 
243
+ def verify_account_email_recently_sent?
244
+ account && (email_last_sent = get_verify_account_email_last_sent) && (Time.now - email_last_sent < verify_account_skip_resend_email_within)
245
+ end
246
+
243
247
  private
244
248
 
245
249
  def _login_form_footer_links
@@ -250,10 +254,6 @@ module Rodauth
250
254
  links
251
255
  end
252
256
 
253
- def verify_account_email_recently_sent?
254
- (email_last_sent = get_verify_account_email_last_sent) && (Time.now - email_last_sent < verify_account_skip_resend_email_within)
255
- end
256
-
257
257
  attr_reader :verify_account_key_value
258
258
 
259
259
  def before_login_attempt
@@ -123,7 +123,7 @@ module Rodauth
123
123
  route(:webauthn_auth_js) do |r|
124
124
  before_webauthn_auth_js_route
125
125
  r.get do
126
- response['Content-Type'] = 'text/javascript'
126
+ set_response_header('content-type', 'text/javascript')
127
127
  webauthn_auth_js
128
128
  end
129
129
  end
@@ -158,7 +158,7 @@ module Rodauth
158
158
  route(:webauthn_setup_js) do |r|
159
159
  before_webauthn_setup_js_route
160
160
  r.get do
161
- response['Content-Type'] = 'text/javascript'
161
+ set_response_header('content-type', 'text/javascript')
162
162
  webauthn_setup_js
163
163
  end
164
164
  end
@@ -410,13 +410,25 @@ module Rodauth
410
410
  private
411
411
 
412
412
  if WebAuthn::VERSION >= '3'
413
- def webauthn_relying_party
414
- # No need to memoize, only called once per request
415
- WebAuthn::RelyingParty.new(
416
- origin: webauthn_origin,
417
- id: webauthn_rp_id,
418
- name: webauthn_rp_name,
419
- )
413
+ if WebAuthn::RelyingParty.instance_method(:initialize).parameters.include?([:key, :allowed_origins])
414
+ def webauthn_relying_party
415
+ # No need to memoize, only called once per request
416
+ WebAuthn::RelyingParty.new(
417
+ allowed_origins: [webauthn_origin],
418
+ id: webauthn_rp_id,
419
+ name: webauthn_rp_name,
420
+ )
421
+ end
422
+ # :nocov:
423
+ else
424
+ def webauthn_relying_party
425
+ WebAuthn::RelyingParty.new(
426
+ origin: webauthn_origin,
427
+ id: webauthn_rp_id,
428
+ name: webauthn_rp_name,
429
+ )
430
+ end
431
+ # :nocov:
420
432
  end
421
433
 
422
434
  def webauthn_create_relying_party_opts
@@ -12,7 +12,7 @@ module Rodauth
12
12
  route(:webauthn_autofill_js) do |r|
13
13
  before_webauthn_autofill_js_route
14
14
  r.get do
15
- response['Content-Type'] = 'text/javascript'
15
+ set_response_header('content-type', 'text/javascript')
16
16
  webauthn_autofill_js
17
17
  end
18
18
  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 = 37
9
+ MINOR = 39
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
@@ -3,6 +3,8 @@
3
3
  require 'securerandom'
4
4
 
5
5
  module Rodauth
6
+ class ConfigurationError < StandardError; end
7
+
6
8
  def self.lib(opts={}, &block)
7
9
  require 'roda'
8
10
  c = Class.new(Roda)
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.37.0
4
+ version: 2.39.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: sequel
@@ -382,7 +381,6 @@ metadata:
382
381
  documentation_uri: https://rodauth.jeremyevans.net/documentation.html
383
382
  mailing_list_uri: https://github.com/jeremyevans/rodauth/discussions
384
383
  source_code_uri: https://github.com/jeremyevans/rodauth
385
- post_install_message:
386
384
  rdoc_options:
387
385
  - "--quiet"
388
386
  - "--line-numbers"
@@ -404,8 +402,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
404
402
  - !ruby/object:Gem::Version
405
403
  version: '0'
406
404
  requirements: []
407
- rubygems_version: 3.5.22
408
- signing_key:
405
+ rubygems_version: 3.6.7
409
406
  specification_version: 4
410
407
  summary: Authentication and Account Management Framework for Rack Applications
411
408
  test_files: []