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 +4 -4
- data/lib/rodauth/features/base.rb +73 -8
- data/lib/rodauth/features/email_auth.rb +4 -4
- data/lib/rodauth/features/http_basic_auth.rb +1 -1
- data/lib/rodauth/features/json.rb +2 -2
- data/lib/rodauth/features/jwt.rb +4 -4
- data/lib/rodauth/features/jwt_cors.rb +5 -5
- data/lib/rodauth/features/lockout.rb +4 -4
- data/lib/rodauth/features/login.rb +2 -1
- data/lib/rodauth/features/reset_password.rb +4 -4
- data/lib/rodauth/features/sms_codes.rb +1 -1
- data/lib/rodauth/features/verify_account.rb +4 -4
- data/lib/rodauth/features/webauthn.rb +21 -9
- data/lib/rodauth/features/webauthn_autofill.rb +1 -1
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +2 -0
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b8c2b404f5a8fad1607ba2bfd64ec4e03579a909ea324a0c9cda7705d9e79f7
|
4
|
+
data.tar.gz: bb2e8dcf2c4afa1d855379b69845c6d5efff8e097b5e95b38a11948c8d381d5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
-
|
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
|
-
|
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['
|
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
|
|
data/lib/rodauth/features/jwt.rb
CHANGED
@@ -47,7 +47,7 @@ module Rodauth
|
|
47
47
|
|
48
48
|
if session_data
|
49
49
|
if jwt_symbolize_deeply?
|
50
|
-
s = JSON.parse(JSON.
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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
|
data/lib/rodauth/version.rb
CHANGED
data/lib/rodauth.rb
CHANGED
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.
|
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:
|
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.
|
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: []
|