rodauth 2.36.0 → 2.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rodauth/features/base.rb +52 -9
  3. data/lib/rodauth/features/change_login.rb +2 -2
  4. data/lib/rodauth/features/create_account.rb +2 -2
  5. data/lib/rodauth/features/email_auth.rb +5 -5
  6. data/lib/rodauth/features/internal_request.rb +4 -4
  7. data/lib/rodauth/features/json.rb +5 -0
  8. data/lib/rodauth/features/jwt.rb +7 -11
  9. data/lib/rodauth/features/lockout.rb +5 -5
  10. data/lib/rodauth/features/login.rb +1 -1
  11. data/lib/rodauth/features/login_password_requirements_base.rb +13 -0
  12. data/lib/rodauth/features/reset_password.rb +5 -5
  13. data/lib/rodauth/features/sms_codes.rb +1 -1
  14. data/lib/rodauth/features/two_factor_base.rb +6 -13
  15. data/lib/rodauth/features/verify_account.rb +6 -6
  16. data/lib/rodauth/features/webauthn_autofill.rb +2 -1
  17. data/lib/rodauth/features/webauthn_login.rb +1 -1
  18. data/lib/rodauth/version.rb +1 -1
  19. data/lib/rodauth.rb +8 -2
  20. metadata +3 -261
  21. data/CHANGELOG +0 -521
  22. data/README.rdoc +0 -1555
  23. data/doc/account_expiration.rdoc +0 -41
  24. data/doc/active_sessions.rdoc +0 -56
  25. data/doc/argon2.rdoc +0 -54
  26. data/doc/audit_logging.rdoc +0 -44
  27. data/doc/base.rdoc +0 -123
  28. data/doc/change_login.rdoc +0 -25
  29. data/doc/change_password.rdoc +0 -26
  30. data/doc/change_password_notify.rdoc +0 -14
  31. data/doc/close_account.rdoc +0 -26
  32. data/doc/confirm_password.rdoc +0 -32
  33. data/doc/create_account.rdoc +0 -27
  34. data/doc/disallow_common_passwords.rdoc +0 -17
  35. data/doc/disallow_password_reuse.rdoc +0 -30
  36. data/doc/email_auth.rdoc +0 -55
  37. data/doc/email_base.rdoc +0 -18
  38. data/doc/error_reasons.rdoc +0 -77
  39. data/doc/guides/admin_activation.rdoc +0 -46
  40. data/doc/guides/already_authenticated.rdoc +0 -10
  41. data/doc/guides/alternative_login.rdoc +0 -46
  42. data/doc/guides/change_table_and_column_names.rdoc +0 -19
  43. data/doc/guides/create_account_programmatically.rdoc +0 -38
  44. data/doc/guides/delay_password.rdoc +0 -25
  45. data/doc/guides/email_only.rdoc +0 -16
  46. data/doc/guides/i18n.rdoc +0 -29
  47. data/doc/guides/internals.rdoc +0 -233
  48. data/doc/guides/links.rdoc +0 -12
  49. data/doc/guides/login_return.rdoc +0 -37
  50. data/doc/guides/migrate_password_hash_algorithm.rdoc +0 -15
  51. data/doc/guides/password_column.rdoc +0 -25
  52. data/doc/guides/password_confirmation.rdoc +0 -37
  53. data/doc/guides/password_requirements.rdoc +0 -43
  54. data/doc/guides/paths.rdoc +0 -51
  55. data/doc/guides/query_params.rdoc +0 -9
  56. data/doc/guides/redirects.rdoc +0 -17
  57. data/doc/guides/registration_field.rdoc +0 -68
  58. data/doc/guides/render_confirmation.rdoc +0 -17
  59. data/doc/guides/require_mfa.rdoc +0 -30
  60. data/doc/guides/reset_password_autologin.rdoc +0 -21
  61. data/doc/guides/share_configuration.rdoc +0 -34
  62. data/doc/guides/status_column.rdoc +0 -28
  63. data/doc/guides/totp_or_recovery.rdoc +0 -16
  64. data/doc/http_basic_auth.rdoc +0 -18
  65. data/doc/internal_request.rdoc +0 -539
  66. data/doc/json.rdoc +0 -56
  67. data/doc/jwt.rdoc +0 -52
  68. data/doc/jwt_cors.rdoc +0 -22
  69. data/doc/jwt_refresh.rdoc +0 -58
  70. data/doc/lockout.rdoc +0 -73
  71. data/doc/login.rdoc +0 -39
  72. data/doc/login_password_requirements_base.rdoc +0 -44
  73. data/doc/logout.rdoc +0 -22
  74. data/doc/otp.rdoc +0 -93
  75. data/doc/otp_lockout_email.rdoc +0 -30
  76. data/doc/otp_modify_email.rdoc +0 -19
  77. data/doc/otp_unlock.rdoc +0 -58
  78. data/doc/password_complexity.rdoc +0 -34
  79. data/doc/password_expiration.rdoc +0 -38
  80. data/doc/password_grace_period.rdoc +0 -24
  81. data/doc/password_pepper.rdoc +0 -52
  82. data/doc/path_class_methods.rdoc +0 -10
  83. data/doc/recovery_codes.rdoc +0 -61
  84. data/doc/release_notes/1.0.0.txt +0 -443
  85. data/doc/release_notes/1.1.0.txt +0 -8
  86. data/doc/release_notes/1.10.0.txt +0 -80
  87. data/doc/release_notes/1.11.0.txt +0 -32
  88. data/doc/release_notes/1.12.0.txt +0 -61
  89. data/doc/release_notes/1.13.0.txt +0 -34
  90. data/doc/release_notes/1.14.0.txt +0 -19
  91. data/doc/release_notes/1.15.0.txt +0 -21
  92. data/doc/release_notes/1.16.0.txt +0 -31
  93. data/doc/release_notes/1.17.0.txt +0 -23
  94. data/doc/release_notes/1.18.0.txt +0 -26
  95. data/doc/release_notes/1.19.0.txt +0 -116
  96. data/doc/release_notes/1.2.0.txt +0 -18
  97. data/doc/release_notes/1.20.0.txt +0 -175
  98. data/doc/release_notes/1.21.0.txt +0 -12
  99. data/doc/release_notes/1.22.0.txt +0 -11
  100. data/doc/release_notes/1.23.0.txt +0 -32
  101. data/doc/release_notes/1.3.0.txt +0 -21
  102. data/doc/release_notes/1.4.0.txt +0 -11
  103. data/doc/release_notes/1.5.0.txt +0 -74
  104. data/doc/release_notes/1.6.0.txt +0 -37
  105. data/doc/release_notes/1.7.0.txt +0 -6
  106. data/doc/release_notes/1.8.0.txt +0 -14
  107. data/doc/release_notes/1.9.0.txt +0 -15
  108. data/doc/release_notes/2.0.0.txt +0 -361
  109. data/doc/release_notes/2.1.0.txt +0 -31
  110. data/doc/release_notes/2.10.0.txt +0 -47
  111. data/doc/release_notes/2.11.0.txt +0 -31
  112. data/doc/release_notes/2.12.0.txt +0 -17
  113. data/doc/release_notes/2.13.0.txt +0 -19
  114. data/doc/release_notes/2.14.0.txt +0 -17
  115. data/doc/release_notes/2.15.0.txt +0 -48
  116. data/doc/release_notes/2.16.0.txt +0 -20
  117. data/doc/release_notes/2.17.0.txt +0 -10
  118. data/doc/release_notes/2.18.0.txt +0 -27
  119. data/doc/release_notes/2.19.0.txt +0 -61
  120. data/doc/release_notes/2.2.0.txt +0 -39
  121. data/doc/release_notes/2.20.0.txt +0 -10
  122. data/doc/release_notes/2.21.0.txt +0 -28
  123. data/doc/release_notes/2.22.0.txt +0 -43
  124. data/doc/release_notes/2.23.0.txt +0 -15
  125. data/doc/release_notes/2.24.0.txt +0 -15
  126. data/doc/release_notes/2.25.0.txt +0 -8
  127. data/doc/release_notes/2.26.0.txt +0 -45
  128. data/doc/release_notes/2.27.0.txt +0 -35
  129. data/doc/release_notes/2.28.0.txt +0 -16
  130. data/doc/release_notes/2.29.0.txt +0 -27
  131. data/doc/release_notes/2.3.0.txt +0 -37
  132. data/doc/release_notes/2.30.0.txt +0 -15
  133. data/doc/release_notes/2.31.0.txt +0 -47
  134. data/doc/release_notes/2.32.0.txt +0 -65
  135. data/doc/release_notes/2.33.0.txt +0 -18
  136. data/doc/release_notes/2.34.0.txt +0 -36
  137. data/doc/release_notes/2.35.0.txt +0 -22
  138. data/doc/release_notes/2.36.0.txt +0 -35
  139. data/doc/release_notes/2.4.0.txt +0 -22
  140. data/doc/release_notes/2.5.0.txt +0 -20
  141. data/doc/release_notes/2.6.0.txt +0 -37
  142. data/doc/release_notes/2.7.0.txt +0 -33
  143. data/doc/release_notes/2.8.0.txt +0 -20
  144. data/doc/release_notes/2.9.0.txt +0 -21
  145. data/doc/remember.rdoc +0 -79
  146. data/doc/reset_password.rdoc +0 -66
  147. data/doc/reset_password_notify.rdoc +0 -17
  148. data/doc/session_expiration.rdoc +0 -28
  149. data/doc/single_session.rdoc +0 -37
  150. data/doc/sms_codes.rdoc +0 -138
  151. data/doc/two_factor_base.rdoc +0 -70
  152. data/doc/update_password_hash.rdoc +0 -7
  153. data/doc/verify_account.rdoc +0 -67
  154. data/doc/verify_account_grace_period.rdoc +0 -19
  155. data/doc/verify_login_change.rdoc +0 -59
  156. data/doc/webauthn.rdoc +0 -118
  157. data/doc/webauthn_autofill.rdoc +0 -19
  158. data/doc/webauthn_login.rdoc +0 -16
  159. data/doc/webauthn_modify_email.rdoc +0 -19
  160. data/doc/webauthn_verify_account.rdoc +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4daf43beb9b2af129683b299f1e455e5b38fa925b1cc020ec79ae951f56f292b
4
- data.tar.gz: a694fe203f76512691004d1138ad03cab7699d317a0a658ae6ac51732ff22b36
3
+ metadata.gz: f79db7dec7147665538bf0b4f8e0c6c554d88396b294f19e5a5ed26543c31d1c
4
+ data.tar.gz: ea377861679a55895bc325b1cf3932970b0de9c773615ba2daecb5805704ae02
5
5
  SHA512:
6
- metadata.gz: aef00a1d31a310ea0061f54ebcb4044ccdcc7106d2f07074f4585ac5406776784bef9c698befa050b374d4dd37dd2495d3a3e4dcd6854e387fc8442901f3f227
7
- data.tar.gz: 85d9837f21b320f2bec1241f07e22ff150b49192017cb1ea20d2863fce93b547d5271cafe30b08baeaa582fd4f40b715ce488d9fb19bd7fe2cc43a2f565b5266
6
+ metadata.gz: 8e13b4dc188867866ee0eda53765091bb48f583d65957e488737b612bde4cf4ae9caa18edd2d88b3c609a7a3e25a51eec42fc5aa25e4083ea51c283310a36cb9
7
+ data.tar.gz: d50e275056bf3196a025e1933ab97ecd88d784c80ec97412cf046bb9718f558da4b321f7fbf52ba09d3554059157ef67f3f407625fd65c237fd2334e170066a7
@@ -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
 
@@ -98,6 +99,7 @@ module Rodauth
98
99
  :inputmode_for_field?,
99
100
  :logged_in?,
100
101
  :login_required,
102
+ :normalize_login,
101
103
  :null_byte_parameter_value,
102
104
  :open_account?,
103
105
  :over_max_bytesize_param_value,
@@ -321,7 +323,7 @@ module Rodauth
321
323
  end
322
324
 
323
325
  def clear_session
324
- if scope.respond_to?(:clear_session)
326
+ if use_scope_clear_session?
325
327
  scope.clear_session
326
328
  else
327
329
  session.clear
@@ -408,6 +410,7 @@ module Rodauth
408
410
 
409
411
  def button_opts(value, opts)
410
412
  opts = Hash[template_opts].merge!(opts)
413
+ _merge_fixed_locals_opts(opts, button_fixed_locals)
411
414
  opts[:locals] = {:value=>value, :opts=>opts}
412
415
  opts[:cache] = cache_templates
413
416
  opts[:cache_key] = :rodauth_button
@@ -505,6 +508,15 @@ module Rodauth
505
508
  nil
506
509
  end
507
510
 
511
+ # The normalized value of the login parameter
512
+ def login_param_value
513
+ normalize_login(param(login_param))
514
+ end
515
+
516
+ def normalize_login(login)
517
+ login
518
+ end
519
+
508
520
  # Return nil by default for values with null bytes
509
521
  def null_byte_parameter_value(key, value)
510
522
  nil
@@ -532,6 +544,12 @@ module Rodauth
532
544
  has_password? ? ['password'] : []
533
545
  end
534
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
+
535
553
  private
536
554
 
537
555
  def _around_rodauth
@@ -545,6 +563,20 @@ module Rodauth
545
563
  s
546
564
  end
547
565
 
566
+ if RUBY_VERSION >= '2.1'
567
+ def button_fixed_locals
568
+ '(value:, opts:)'
569
+ end
570
+ # :nocov:
571
+ else
572
+ # Work on Ruby 2.0 when using Tilt 2.6+, as Ruby 2.0 does
573
+ # not support required keyword arguments.
574
+ def button_fixed_locals
575
+ '(value: nil, opts: nil)'
576
+ end
577
+ end
578
+ # :nocov:
579
+
548
580
  def database_function_password_match?(name, hash_id, password, salt)
549
581
  db.get(Sequel.function(function_name(name), hash_id, password_hash_using_salt(password, salt)))
550
582
  end
@@ -708,12 +740,6 @@ module Rodauth
708
740
  end
709
741
  end
710
742
 
711
- def has_password?
712
- return @has_password if defined?(@has_password)
713
- return false unless account || session_value
714
- @has_password = !!get_password_hash
715
- end
716
-
717
743
  def password_hash_using_salt(password, salt)
718
744
  BCrypt::Engine.hash_secret(password, salt)
719
745
  end
@@ -756,7 +782,7 @@ module Rodauth
756
782
  end
757
783
 
758
784
  def compute_raw_hmac(data)
759
- raise ArgumentError, "hmac_secret not set" unless hmac_secret
785
+ raise ConfigurationError, "hmac_secret not set" unless hmac_secret
760
786
  compute_raw_hmac_with_secret(data, hmac_secret)
761
787
  end
762
788
 
@@ -869,9 +895,13 @@ module Rodauth
869
895
  false
870
896
  end
871
897
 
898
+ def use_scope_clear_session?
899
+ scope.respond_to?(:clear_session)
900
+ end
901
+
872
902
  def require_response(meth)
873
903
  send(meth)
874
- 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."
904
+ raise ConfigurationError, "#{meth.to_s.sub(/\A_/, '')} overridden without returning a response (should use redirect or request.halt)."
875
905
  end
876
906
 
877
907
  def set_session_value(key, value)
@@ -898,6 +928,7 @@ module Rodauth
898
928
 
899
929
  def _view_opts(page)
900
930
  opts = template_opts.dup
931
+ _merge_fixed_locals_opts(opts, '(rodauth: self.rodauth)')
901
932
  opts[:locals] = opts[:locals] ? opts[:locals].dup : {}
902
933
  opts[:locals][:rodauth] = self
903
934
  opts[:cache] = cache_templates
@@ -905,6 +936,14 @@ module Rodauth
905
936
  _template_opts(opts, page)
906
937
  end
907
938
 
939
+ def _merge_fixed_locals_opts(opts, fixed_locals)
940
+ if use_template_fixed_locals? && !opts[:locals]
941
+ fixed_locals_opts = {default_fixed_locals: fixed_locals}
942
+ fixed_locals_opts.merge!(opts[:template_opts]) if opts[:template_opts]
943
+ opts[:template_opts] = fixed_locals_opts
944
+ end
945
+ end
946
+
908
947
  # Set the template path only if there isn't an overridden template in the application.
909
948
  # Result should replace existing template opts.
910
949
  def _template_opts(opts, page)
@@ -916,6 +955,10 @@ module Rodauth
916
955
  end
917
956
 
918
957
  def _view(meth, page)
958
+ unless scope.respond_to?(meth)
959
+ raise ConfigurationError, "attempted to render a built-in view/email template (#{page.inspect}), but rendering is disabled"
960
+ end
961
+
919
962
  scope.send(meth, _view_opts(page))
920
963
  end
921
964
  end
@@ -36,12 +36,12 @@ module Rodauth
36
36
  throw_error_reason(:invalid_password, invalid_password_error_status, password_param, invalid_password_message)
37
37
  end
38
38
 
39
- login = param(login_param)
39
+ login = login_param_value
40
40
  unless login_meets_requirements?(login)
41
41
  throw_error_status(invalid_field_error_status, login_param, login_does_not_meet_requirements_message)
42
42
  end
43
43
 
44
- if require_login_confirmation? && login != param(login_confirm_param)
44
+ if require_login_confirmation? && !login_confirmation_matches?(login, param(login_confirm_param))
45
45
  throw_error_reason(:logins_do_not_match, unmatched_field_error_status, login_param, logins_do_not_match_message)
46
46
  end
47
47
 
@@ -40,12 +40,12 @@ module Rodauth
40
40
  end
41
41
 
42
42
  r.post do
43
- login = param(login_param)
43
+ login = login_param_value
44
44
  password = param(password_param)
45
45
  new_account(login)
46
46
 
47
47
  catch_error do
48
- if require_login_confirmation? && login != param(login_confirm_param)
48
+ if require_login_confirmation? && !login_confirmation_matches?(login, param(login_confirm_param))
49
49
  throw_error_reason(:logins_do_not_match, unmatched_field_error_status, login_param, logins_do_not_match_message)
50
50
  end
51
51
 
@@ -56,7 +56,7 @@ module Rodauth
56
56
  before_email_auth_request_route
57
57
 
58
58
  r.post do
59
- if account_from_login(param(login_param)) && open_account?
59
+ if account_from_login(login_param_value) && open_account?
60
60
  _email_auth_request
61
61
  end
62
62
 
@@ -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
@@ -223,14 +223,14 @@ module Rodauth
223
223
  end
224
224
 
225
225
  def _handle_account_id_for_login(_)
226
- raise InternalRequestError, "no login provided" unless login = param_or_nil(login_param)
227
- raise InternalRequestError, "no account for login" unless account = account_from_login(login)
226
+ raise InternalRequestError, "no login provided" unless param_or_nil(login_param)
227
+ raise InternalRequestError, "no account for login" unless account = account_from_login(login_param_value)
228
228
  _return_from_internal_request(account[account_id_column])
229
229
  end
230
230
 
231
231
  def _handle_account_exists?(_)
232
- raise InternalRequestError, "no login provided" unless login = param_or_nil(login_param)
233
- _return_from_internal_request(!!account_from_login(login))
232
+ raise InternalRequestError, "no login provided" unless param_or_nil(login_param)
233
+ _return_from_internal_request(!!account_from_login(login_param_value))
234
234
  end
235
235
 
236
236
  def _handle_lock_account(_)
@@ -72,6 +72,11 @@ module Rodauth
72
72
 
73
73
  private
74
74
 
75
+ def check_csrf?
76
+ return false if use_json?
77
+ super
78
+ end
79
+
75
80
  def _set_otp_unlock_info
76
81
  if use_json?
77
82
  json_response[:num_successes] = otp_unlock_num_successes
@@ -60,14 +60,11 @@ module Rodauth
60
60
 
61
61
  def clear_session
62
62
  super
63
- if use_jwt?
64
- session.clear
65
- set_jwt
66
- end
63
+ set_jwt if use_jwt?
67
64
  end
68
65
 
69
66
  def jwt_secret
70
- raise ArgumentError, "jwt_secret not set"
67
+ raise ConfigurationError, "jwt_secret not set"
71
68
  end
72
69
 
73
70
  def jwt_session_hash
@@ -104,16 +101,11 @@ module Rodauth
104
101
 
105
102
  private
106
103
 
107
- def check_csrf?
108
- return false if use_jwt?
109
- super
110
- end
111
-
112
104
  def _jwt_decode_opts
113
105
  jwt_decode_opts
114
106
  end
115
107
 
116
- if JWT::VERSION::MAJOR > 2 || (JWT::VERSION::MAJOR == 2 && JWT::VERSION::MINOR >= 4)
108
+ if JWT.gem_version >= Gem::Version.new("2.4")
117
109
  def _jwt_decode_secrets
118
110
  secrets = [jwt_secret, jwt_old_secret]
119
111
  secrets.compact!
@@ -158,5 +150,9 @@ module Rodauth
158
150
  def set_jwt
159
151
  set_jwt_token(session_jwt)
160
152
  end
153
+
154
+ def use_scope_clear_session?
155
+ super && !use_jwt?
156
+ end
161
157
  end
162
158
  end
@@ -70,7 +70,7 @@ module Rodauth
70
70
  before_unlock_account_request_route
71
71
 
72
72
  r.post do
73
- if account_from_login(param(login_param)) && get_unlock_account_key
73
+ if account_from_login(login_param_value) && get_unlock_account_key
74
74
  if unlock_account_email_recently_sent?
75
75
  set_redirect_error_flash unlock_account_email_recently_sent_error_flash
76
76
  redirect unlock_account_email_recently_sent_redirect
@@ -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
@@ -45,7 +45,7 @@ module Rodauth
45
45
  view = :login_view
46
46
 
47
47
  catch_error do
48
- unless account_from_login(param(login_param))
48
+ unless account_from_login(login_param_value)
49
49
  throw_error_reason(:no_matching_login, no_matching_login_error_status, login_param, no_matching_login_message)
50
50
  end
51
51
 
@@ -36,6 +36,7 @@ module Rodauth
36
36
  )
37
37
 
38
38
  auth_methods(
39
+ :login_confirmation_matches?,
39
40
  :login_meets_requirements?,
40
41
  :login_valid_email?,
41
42
  :password_hash,
@@ -126,6 +127,18 @@ module Rodauth
126
127
  @login_requirement_message = message
127
128
  end
128
129
 
130
+ if RUBY_VERSION >= '2.4'
131
+ def login_confirmation_matches?(login, login_confirmation)
132
+ login.casecmp?(login_confirmation)
133
+ end
134
+ # :nocov:
135
+ else
136
+ def login_confirmation_matches?(login, login_confirmation)
137
+ login.casecmp(login_confirmation) == 0
138
+ end
139
+ # :nocov:
140
+ end
141
+
129
142
  def login_meets_length_requirements?(login)
130
143
  if login_minimum_length > login.length
131
144
  set_login_requirement_error_message(:login_too_short, login_too_short_message)
@@ -69,7 +69,7 @@ module Rodauth
69
69
 
70
70
  r.post do
71
71
  catch_error do
72
- unless account_from_login(param(login_param))
72
+ unless account_from_login(login_param_value)
73
73
  throw_error_reason(:no_matching_login, no_matching_login_error_status, login_param, no_matching_login_message)
74
74
  end
75
75
 
@@ -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)
@@ -124,23 +124,12 @@ module Rodauth
124
124
  end
125
125
 
126
126
  def authenticated?
127
- # False if not authenticated via single factor
128
- return false unless super
129
-
130
- # True if already authenticated via 2nd factor
131
- return true if two_factor_authenticated?
132
-
133
- # True if authenticated via single factor and 2nd factor not setup
134
- !uses_two_factor_authentication?
127
+ super && !two_factor_partially_authenticated?
135
128
  end
136
129
 
137
130
  def require_authentication
138
131
  super
139
-
140
- # Avoid database query if already authenticated via 2nd factor
141
- return if two_factor_authenticated?
142
-
143
- require_two_factor_authenticated if uses_two_factor_authentication?
132
+ require_two_factor_authenticated if two_factor_partially_authenticated?
144
133
  end
145
134
 
146
135
  def require_two_factor_setup
@@ -188,6 +177,10 @@ module Rodauth
188
177
  end
189
178
  end
190
179
 
180
+ def two_factor_partially_authenticated?
181
+ logged_in? && !two_factor_authenticated? && uses_two_factor_authentication?
182
+ end
183
+
191
184
  def two_factor_authenticated?
192
185
  authenticated_by && authenticated_by.length >= 2
193
186
  end
@@ -71,7 +71,7 @@ module Rodauth
71
71
  end
72
72
 
73
73
  r.post do
74
- if account_from_login(param(login_param)) && allow_resending_verify_account_email?
74
+ if account_from_login(login_param_value) && allow_resending_verify_account_email?
75
75
  if verify_account_email_recently_sent?
76
76
  set_redirect_error_flash verify_account_email_recently_sent_error_flash
77
77
  redirect verify_account_email_recently_sent_redirect
@@ -240,20 +240,20 @@ 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
246
250
  links = super
247
- if !param_or_nil(login_param) || ((account || account_from_login(param(login_param))) && allow_resending_verify_account_email?)
251
+ if !param_or_nil(login_param) || ((account || account_from_login(login_param_value)) && allow_resending_verify_account_email?)
248
252
  links << [30, verify_account_resend_path, verify_account_resend_link_text]
249
253
  end
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
@@ -4,6 +4,7 @@ module Rodauth
4
4
  Feature.define(:webauthn_autofill, :WebauthnAutofill) do
5
5
  depends :webauthn_login
6
6
 
7
+ auth_value_method :webauthn_autofill?, true
7
8
  auth_value_method :webauthn_autofill_js, File.binread(File.expand_path('../../../../javascript/webauthn_autofill.js', __FILE__)).freeze
8
9
 
9
10
  translatable_method :webauthn_invalid_webauthn_id_message, "no webauthn key with given id found"
@@ -37,7 +38,7 @@ module Rodauth
37
38
 
38
39
  def _login_form_footer
39
40
  footer = super
40
- footer += render("webauthn-autofill") unless valid_login_entered?
41
+ footer += render("webauthn-autofill") if webauthn_autofill? && !valid_login_entered?
41
42
  footer
42
43
  end
43
44
 
@@ -74,7 +74,7 @@ module Rodauth
74
74
  end
75
75
 
76
76
  def account_from_webauthn_login
77
- account_from_login(param(login_param))
77
+ account_from_login(login_param_value)
78
78
  end
79
79
 
80
80
  def webauthn_login_options?
@@ -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 = 36
9
+ MINOR = 38
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)
@@ -402,7 +404,11 @@ module Rodauth
402
404
  end
403
405
 
404
406
  module InstanceMethods
405
- def rodauth(name=nil)
407
+ def default_rodauth_name
408
+ nil
409
+ end
410
+
411
+ def rodauth(name=default_rodauth_name)
406
412
  if name
407
413
  (@_rodauths ||= {})[name] ||= self.class.rodauth(name).new(self)
408
414
  else
@@ -440,7 +446,7 @@ module Rodauth
440
446
  end
441
447
 
442
448
  module RequestMethods
443
- def rodauth(name=nil)
449
+ def rodauth(name=scope.default_rodauth_name)
444
450
  scope.rodauth(name).route!
445
451
  end
446
452
  end