rodauth 1.23.0 → 2.0.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +132 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +207 -79
  5. data/doc/account_expiration.rdoc +12 -26
  6. data/doc/active_sessions.rdoc +49 -0
  7. data/doc/audit_logging.rdoc +44 -0
  8. data/doc/base.rdoc +74 -128
  9. data/doc/change_login.rdoc +7 -14
  10. data/doc/change_password.rdoc +9 -13
  11. data/doc/change_password_notify.rdoc +2 -2
  12. data/doc/close_account.rdoc +9 -16
  13. data/doc/confirm_password.rdoc +12 -5
  14. data/doc/create_account.rdoc +11 -22
  15. data/doc/disallow_password_reuse.rdoc +6 -13
  16. data/doc/email_auth.rdoc +15 -14
  17. data/doc/email_base.rdoc +5 -15
  18. data/doc/http_basic_auth.rdoc +10 -1
  19. data/doc/jwt.rdoc +22 -22
  20. data/doc/jwt_cors.rdoc +2 -3
  21. data/doc/jwt_refresh.rdoc +12 -8
  22. data/doc/lockout.rdoc +17 -15
  23. data/doc/login.rdoc +10 -2
  24. data/doc/login_password_requirements_base.rdoc +15 -37
  25. data/doc/logout.rdoc +2 -2
  26. data/doc/otp.rdoc +24 -19
  27. data/doc/password_complexity.rdoc +10 -26
  28. data/doc/password_expiration.rdoc +11 -25
  29. data/doc/password_grace_period.rdoc +16 -2
  30. data/doc/recovery_codes.rdoc +18 -12
  31. data/doc/release_notes/2.0.0.txt +361 -0
  32. data/doc/remember.rdoc +40 -64
  33. data/doc/reset_password.rdoc +12 -9
  34. data/doc/session_expiration.rdoc +1 -0
  35. data/doc/single_session.rdoc +16 -25
  36. data/doc/sms_codes.rdoc +24 -14
  37. data/doc/two_factor_base.rdoc +60 -22
  38. data/doc/verify_account.rdoc +14 -12
  39. data/doc/verify_account_grace_period.rdoc +6 -2
  40. data/doc/verify_login_change.rdoc +9 -8
  41. data/doc/webauthn.rdoc +115 -0
  42. data/doc/webauthn_login.rdoc +15 -0
  43. data/doc/webauthn_verify_account.rdoc +9 -0
  44. data/javascript/webauthn_auth.js +45 -0
  45. data/javascript/webauthn_setup.js +35 -0
  46. data/lib/roda/plugins/rodauth.rb +1 -1
  47. data/lib/rodauth.rb +29 -24
  48. data/lib/rodauth/features/account_expiration.rb +5 -5
  49. data/lib/rodauth/features/active_sessions.rb +160 -0
  50. data/lib/rodauth/features/audit_logging.rb +96 -0
  51. data/lib/rodauth/features/base.rb +131 -47
  52. data/lib/rodauth/features/change_password_notify.rb +1 -1
  53. data/lib/rodauth/features/confirm_password.rb +40 -2
  54. data/lib/rodauth/features/create_account.rb +7 -13
  55. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  56. data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
  57. data/lib/rodauth/features/email_auth.rb +29 -27
  58. data/lib/rodauth/features/email_base.rb +3 -3
  59. data/lib/rodauth/features/http_basic_auth.rb +44 -37
  60. data/lib/rodauth/features/jwt.rb +51 -8
  61. data/lib/rodauth/features/jwt_refresh.rb +3 -3
  62. data/lib/rodauth/features/lockout.rb +11 -13
  63. data/lib/rodauth/features/login.rb +48 -8
  64. data/lib/rodauth/features/login_password_requirements_base.rb +4 -4
  65. data/lib/rodauth/features/otp.rb +71 -81
  66. data/lib/rodauth/features/password_complexity.rb +4 -11
  67. data/lib/rodauth/features/password_expiration.rb +1 -1
  68. data/lib/rodauth/features/password_grace_period.rb +17 -10
  69. data/lib/rodauth/features/recovery_codes.rb +47 -51
  70. data/lib/rodauth/features/remember.rb +11 -27
  71. data/lib/rodauth/features/reset_password.rb +25 -25
  72. data/lib/rodauth/features/session_expiration.rb +6 -4
  73. data/lib/rodauth/features/single_session.rb +7 -5
  74. data/lib/rodauth/features/sms_codes.rb +58 -67
  75. data/lib/rodauth/features/two_factor_base.rb +132 -28
  76. data/lib/rodauth/features/verify_account.rb +23 -20
  77. data/lib/rodauth/features/verify_account_grace_period.rb +19 -8
  78. data/lib/rodauth/features/verify_login_change.rb +11 -10
  79. data/lib/rodauth/features/webauthn.rb +507 -0
  80. data/lib/rodauth/features/webauthn_login.rb +70 -0
  81. data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
  82. data/lib/rodauth/version.rb +2 -2
  83. data/templates/button.str +1 -3
  84. data/templates/change-login.str +1 -2
  85. data/templates/change-password.str +3 -5
  86. data/templates/close-account.str +2 -2
  87. data/templates/confirm-password.str +1 -1
  88. data/templates/create-account.str +1 -1
  89. data/templates/email-auth-request-form.str +1 -2
  90. data/templates/email-auth.str +1 -1
  91. data/templates/global-logout-field.str +6 -0
  92. data/templates/login-confirm-field.str +2 -4
  93. data/templates/login-display.str +3 -2
  94. data/templates/login-field.str +2 -4
  95. data/templates/login-form-footer.str +6 -0
  96. data/templates/login-form.str +7 -0
  97. data/templates/login.str +1 -9
  98. data/templates/logout.str +1 -1
  99. data/templates/multi-phase-login.str +3 -0
  100. data/templates/otp-auth-code-field.str +5 -3
  101. data/templates/otp-auth.str +1 -1
  102. data/templates/otp-disable.str +1 -1
  103. data/templates/otp-setup.str +3 -3
  104. data/templates/password-confirm-field.str +2 -4
  105. data/templates/password-field.str +2 -4
  106. data/templates/recovery-auth.str +3 -6
  107. data/templates/recovery-codes.str +1 -1
  108. data/templates/remember.str +15 -20
  109. data/templates/reset-password-request.str +2 -2
  110. data/templates/reset-password.str +1 -2
  111. data/templates/sms-auth.str +1 -1
  112. data/templates/sms-code-field.str +5 -3
  113. data/templates/sms-confirm.str +1 -2
  114. data/templates/sms-disable.str +1 -2
  115. data/templates/sms-request.str +1 -1
  116. data/templates/sms-setup.str +6 -4
  117. data/templates/two-factor-auth.str +5 -0
  118. data/templates/two-factor-disable.str +6 -0
  119. data/templates/two-factor-manage.str +16 -0
  120. data/templates/unlock-account-request.str +2 -2
  121. data/templates/unlock-account.str +1 -1
  122. data/templates/verify-account-resend.str +1 -1
  123. data/templates/verify-account.str +1 -2
  124. data/templates/verify-login-change.str +1 -1
  125. data/templates/webauthn-auth.str +11 -0
  126. data/templates/webauthn-remove.str +14 -0
  127. data/templates/webauthn-setup.str +12 -0
  128. metadata +64 -11
  129. data/doc/verify_change_login.rdoc +0 -11
  130. data/lib/rodauth/features/verify_change_login.rb +0 -20
@@ -4,7 +4,7 @@ module Rodauth
4
4
  Feature.define(:change_password_notify, :ChangePasswordNotify) do
5
5
  depends :change_password, :email_base
6
6
 
7
- auth_value_method :password_changed_email_subject, 'Password Changed'
7
+ translatable_method :password_changed_email_subject, 'Password Changed'
8
8
 
9
9
  auth_value_methods(
10
10
  :password_changed_email_body
@@ -4,20 +4,26 @@ module Rodauth
4
4
  Feature.define(:confirm_password, :ConfirmPassword) do
5
5
  notice_flash "Your password has been confirmed"
6
6
  error_flash "There was an error confirming your password"
7
+ error_flash "You need to confirm your password before continuing", 'password_authentication_required'
7
8
  loaded_templates %w'confirm-password password-field'
8
9
  view 'confirm-password', 'Confirm Password'
9
10
  additional_form_tags
10
11
  button 'Confirm Password'
11
12
  before
12
13
  after
14
+ redirect(:password_authentication_required){confirm_password_path}
13
15
 
14
16
  session_key :confirm_password_redirect_session_key, :confirm_password_redirect
17
+ translatable_method :confirm_password_link_text, "Enter Password"
18
+ auth_value_method :password_authentication_required_error_status, 401
19
+
15
20
  auth_value_methods :confirm_password_redirect
16
21
 
17
22
  auth_methods :confirm_password
18
23
 
19
24
  route do |r|
20
- require_account
25
+ require_login
26
+ require_account_session
21
27
  before_confirm_password_route
22
28
 
23
29
  request.get do
@@ -42,12 +48,44 @@ module Rodauth
42
48
  end
43
49
  end
44
50
 
51
+ def require_password_authentication
52
+ require_login
53
+
54
+ if require_password_authentication? && has_password?
55
+ set_redirect_error_status(password_authentication_required_error_status)
56
+ set_redirect_error_flash password_authentication_required_error_flash
57
+ set_session_value(confirm_password_redirect_session_key, request.fullpath)
58
+ redirect password_authentication_required_redirect
59
+ end
60
+ end
61
+
45
62
  def confirm_password
63
+ authenticated_by.delete('autologin')
64
+ authenticated_by.delete('remember')
65
+ authenticated_by.delete('email_auth')
66
+ authenticated_by.delete('password')
67
+ authenticated_by.unshift("password")
68
+ remove_session_value(autologin_type_session_key)
46
69
  nil
47
70
  end
48
71
 
49
72
  def confirm_password_redirect
50
- session.delete(confirm_password_redirect_session_key) || default_redirect
73
+ remove_session_value(confirm_password_redirect_session_key) || default_redirect
74
+ end
75
+
76
+ private
77
+
78
+ def _two_factor_auth_links
79
+ links = (super if defined?(super)) || []
80
+ if authenticated_by.length == 1 && !authenticated_by.include?('password') && has_password?
81
+ links << [5, confirm_password_path, confirm_password_link_text]
82
+ end
83
+ links
84
+ end
85
+
86
+ def require_password_authentication?
87
+ return true if defined?(super) && super
88
+ !authenticated_by.include?('password')
51
89
  end
52
90
  end
53
91
  end
@@ -2,9 +2,8 @@
2
2
 
3
3
  module Rodauth
4
4
  Feature.define(:create_account, :CreateAccount) do
5
- depends :login_password_requirements_base
5
+ depends :login, :login_password_requirements_base
6
6
 
7
- depends :login
8
7
  notice_flash 'Your account has been created'
9
8
  error_flash "There was an error creating your account"
10
9
  loaded_templates %w'create-account login-field login-confirm-field password-field password-confirm-field'
@@ -16,10 +15,9 @@ module Rodauth
16
15
  redirect
17
16
 
18
17
  auth_value_method :create_account_autologin?, true
18
+ translatable_method :create_account_link_text, "Create a New Account"
19
19
  auth_value_method :create_account_set_password?, true
20
20
 
21
- auth_value_methods :create_account_link
22
-
23
21
  auth_methods(
24
22
  :save_account,
25
23
  :set_new_account_password
@@ -76,7 +74,7 @@ module Rodauth
76
74
  end
77
75
  after_create_account
78
76
  if create_account_autologin?
79
- update_session
77
+ autologin_session('create_account')
80
78
  end
81
79
  set_notice_flash create_account_notice_flash
82
80
  redirect create_account_redirect
@@ -88,14 +86,6 @@ module Rodauth
88
86
  end
89
87
  end
90
88
 
91
- def create_account_link
92
- "<p><a href=\"#{create_account_path}\">Create a New Account</a></p>"
93
- end
94
-
95
- def login_form_footer
96
- super + create_account_link
97
- end
98
-
99
89
  def set_new_account_password(password)
100
90
  account[account_password_hash_column] = password_hash(password)
101
91
  end
@@ -121,6 +111,10 @@ module Rodauth
121
111
 
122
112
  private
123
113
 
114
+ def _login_form_footer_links
115
+ super << [10, create_account_path, create_account_link_text]
116
+ end
117
+
124
118
  def _new_account(login)
125
119
  acc = {login_column=>login}
126
120
  unless skip_status_checks?
@@ -5,7 +5,7 @@ module Rodauth
5
5
  depends :login_password_requirements_base
6
6
 
7
7
  auth_value_method :most_common_passwords_file, File.expand_path('../../../../dict/top-10_000-passwords.txt', __FILE__)
8
- auth_value_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
8
+ translatable_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
9
9
  auth_value_method :most_common_passwords, nil
10
10
 
11
11
  auth_methods :password_one_of_most_common?
@@ -4,7 +4,7 @@ module Rodauth
4
4
  Feature.define(:disallow_password_reuse, :DisallowPasswordReuse) do
5
5
  depends :login_password_requirements_base
6
6
 
7
- auth_value_method :password_same_as_previous_password_message, "same as previous password"
7
+ translatable_method :password_same_as_previous_password_message, "same as previous password"
8
8
  auth_value_method :previous_password_account_id_column, :account_id
9
9
  auth_value_method :previous_password_hash_column, :password_hash
10
10
  auth_value_method :previous_password_hash_table, :account_previous_password_hashes
@@ -4,8 +4,6 @@ module Rodauth
4
4
  Feature.define(:email_auth, :EmailAuth) do
5
5
  depends :login, :email_base
6
6
 
7
- def_deprecated_alias :no_matching_email_auth_key_error_flash, :no_matching_email_auth_key_message
8
-
9
7
  notice_flash "An email has been sent to you with a link to login to your account", 'email_auth_email_sent'
10
8
  error_flash "There was an error logging you in"
11
9
  error_flash "There was an error requesting an email link to authenticate", 'email_auth_request'
@@ -23,17 +21,16 @@ module Rodauth
23
21
  redirect(:email_auth_email_recently_sent){default_post_email_redirect}
24
22
 
25
23
  auth_value_method :email_auth_deadline_column, :deadline
26
- auth_value_method :email_auth_deadline_interval, {:days=>1}
27
- auth_value_method :email_auth_email_subject, 'Login Link'
24
+ auth_value_method :email_auth_deadline_interval, {:days=>1}.freeze
25
+ translatable_method :email_auth_email_subject, 'Login Link'
28
26
  auth_value_method :email_auth_id_column, :id
29
27
  auth_value_method :email_auth_key_column, :key
30
28
  auth_value_method :email_auth_key_param, 'key'
31
29
  auth_value_method :email_auth_email_last_sent_column, :email_last_sent
32
30
  auth_value_method :email_auth_skip_resend_email_within, 300
33
31
  auth_value_method :email_auth_table, :account_email_auth_keys
32
+ auth_value_method :force_email_auth?, false
34
33
  session_key :email_auth_session_key, :email_auth_key
35
-
36
- auth_value_methods :force_email_auth?
37
34
 
38
35
  auth_methods(
39
36
  :create_email_auth_email,
@@ -74,7 +71,7 @@ module Rodauth
74
71
 
75
72
  r.get do
76
73
  if key = param_or_nil(email_auth_key_param)
77
- session[email_auth_session_key] = key
74
+ set_session_value(email_auth_session_key, key)
78
75
  redirect(r.path)
79
76
  end
80
77
 
@@ -82,7 +79,7 @@ module Rodauth
82
79
  if account_from_email_auth_key(key)
83
80
  email_auth_view
84
81
  else
85
- session[email_auth_session_key] = nil
82
+ remove_session_value(email_auth_session_key)
86
83
  set_redirect_error_flash no_matching_email_auth_key_error_flash
87
84
  redirect require_login_redirect
88
85
  end
@@ -97,7 +94,7 @@ module Rodauth
97
94
  redirect email_auth_email_sent_redirect
98
95
  end
99
96
 
100
- _login
97
+ _login('email_auth')
101
98
  end
102
99
  end
103
100
 
@@ -148,43 +145,44 @@ module Rodauth
148
145
  ds.get(email_auth_key_column)
149
146
  end
150
147
 
151
- def login_form_footer
152
- footer = super
153
- footer += email_auth_request_form if valid_login_entered?
154
- footer
155
- end
156
-
157
148
  def email_auth_request_form
158
149
  render('email-auth-request-form')
159
150
  end
160
151
 
161
152
  def after_login_entered_during_multi_phase_login
162
- if force_email_auth?
163
- # If the account does not have a password hash, just send the
164
- # email link.
165
- _email_auth_request
166
- redirect email_auth_email_sent_redirect
167
- else
168
- # If the account has a password hash, allow password login, but
169
- # we will show form below to also login via email link.
170
- super
171
- end
153
+ # If forcing email auth, just send the email link.
154
+ _email_auth_request_and_redirect if force_email_auth?
155
+
156
+ super
172
157
  end
173
158
 
174
159
  def use_multi_phase_login?
175
160
  true
176
161
  end
177
162
 
178
- def force_email_auth?
179
- get_password_hash.nil?
163
+ def possible_authentication_methods
164
+ methods = super
165
+ methods << 'email_auth' if !methods.include?('password') && allow_email_auth?
166
+ methods
180
167
  end
181
168
 
182
169
  private
183
170
 
171
+ def _multi_phase_login_forms
172
+ forms = super
173
+ forms << [30, email_auth_request_form, :_email_auth_request_and_redirect] if valid_login_entered? && allow_email_auth?
174
+ forms
175
+ end
176
+
184
177
  def email_auth_email_recently_sent?
185
178
  (email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
186
179
  end
187
180
 
181
+ def _email_auth_request_and_redirect
182
+ _email_auth_request
183
+ redirect email_auth_email_sent_redirect
184
+ end
185
+
188
186
  def _email_auth_request
189
187
  if email_auth_email_recently_sent?
190
188
  set_redirect_error_flash email_auth_email_recently_sent_error_flash
@@ -204,6 +202,10 @@ module Rodauth
204
202
 
205
203
  attr_reader :email_auth_key_value
206
204
 
205
+ def allow_email_auth?
206
+ defined?(super) ? super : true
207
+ end
208
+
207
209
  def after_login
208
210
  # Remove the email auth key after any login, even if
209
211
  # it is a password login. This is done to invalidate
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rodauth
4
4
  Feature.define(:email_base, :EmailBase) do
5
- auth_value_method :email_subject_prefix, nil
5
+ translatable_method :email_subject_prefix, ''
6
6
  auth_value_method :require_mail?, true
7
7
  auth_value_method :allow_raw_email_token?, false
8
8
 
@@ -43,7 +43,7 @@ module Rodauth
43
43
  end
44
44
 
45
45
  def email_from
46
- "webmaster@#{request.host}"
46
+ "webmaster@#{domain}"
47
47
  end
48
48
 
49
49
  def email_to
@@ -51,7 +51,7 @@ module Rodauth
51
51
  end
52
52
 
53
53
  def token_link(route, param, key)
54
- "#{route_url(route)}?#{param}=#{account_id}#{token_separator}#{convert_email_token_key(key)}"
54
+ route_url(route, param => "#{account_id}#{token_separator}#{convert_email_token_key(key)}")
55
55
  end
56
56
 
57
57
  def convert_email_token_key(key)
@@ -3,54 +3,61 @@
3
3
  module Rodauth
4
4
  Feature.define(:http_basic_auth, :HttpBasicAuth) do
5
5
  auth_value_method :http_basic_auth_realm, "protected"
6
- auth_value_method :require_http_basic_auth, false
6
+ auth_value_method :require_http_basic_auth?, false
7
7
 
8
- def session
9
- return @session if defined?(@session)
10
- sess = super
11
- return sess if sess[session_key]
12
- return sess unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
13
- username, password = token.unpack("m*").first.split(/:/, 2)
14
-
15
- if username && password
16
- catch_error do
17
- unless account_from_login(username)
18
- throw_basic_auth_error(login_param, no_matching_login_message)
19
- end
20
-
21
- before_login_attempt
22
-
23
- unless open_account?
24
- throw_basic_auth_error(login_param, no_matching_login_message)
25
- end
26
-
27
- unless password_match?(password)
28
- after_login_failure
29
- throw_basic_auth_error(password_param, invalid_password_message)
30
- end
31
-
32
- transaction do
33
- before_login
34
- sess[session_key] = account_session_value
35
- after_login
36
- end
37
- end
8
+ def require_login
9
+ if require_http_basic_auth?
10
+ require_http_basic_auth
11
+ elsif !logged_in?
12
+ http_basic_auth
38
13
  end
39
14
 
40
- sess
15
+ super
41
16
  end
42
17
 
43
- private
44
-
45
- def require_login
46
- if !logged_in? && require_http_basic_auth
18
+ def require_http_basic_auth
19
+ unless http_basic_auth
47
20
  set_http_basic_auth_error_response
48
21
  request.halt
49
22
  end
23
+ end
50
24
 
51
- super
25
+ def http_basic_auth
26
+ return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
27
+
28
+ username, password = token.unpack("m*").first.split(/:/, 2)
29
+ return unless username && password
30
+
31
+ catch_error do
32
+ unless account_from_login(username)
33
+ throw_basic_auth_error(login_param, no_matching_login_message)
34
+ end
35
+
36
+ before_login_attempt
37
+
38
+ unless open_account?
39
+ throw_basic_auth_error(login_param, no_matching_login_message)
40
+ end
41
+
42
+ unless password_match?(password)
43
+ after_login_failure
44
+ throw_basic_auth_error(password_param, invalid_password_message)
45
+ end
46
+
47
+ transaction do
48
+ before_login
49
+ login_session('password')
50
+ after_login
51
+ end
52
+
53
+ return true
54
+ end
55
+
56
+ nil
52
57
  end
53
58
 
59
+ private
60
+
54
61
  def set_http_basic_auth_error_response
55
62
  response.status = 401
56
63
  response.headers["WWW-Authenticate"] = "Basic realm=\"#{http_basic_auth_realm}\""
@@ -4,25 +4,25 @@ require 'jwt'
4
4
 
5
5
  module Rodauth
6
6
  Feature.define(:jwt, :Jwt) do
7
- auth_value_method :invalid_jwt_format_error_message, "invalid JWT format or claim in Authorization header"
8
- auth_value_method :json_non_post_error_message, 'non-POST method used in JSON API'
9
- auth_value_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
7
+ translatable_method :invalid_jwt_format_error_message, "invalid JWT format or claim in Authorization header"
8
+ translatable_method :json_non_post_error_message, 'non-POST method used in JSON API'
9
+ translatable_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
10
10
  auth_value_method :json_accept_regexp, /(?:(?:\*|\bapplication)\/\*|\bapplication\/(?:vnd\.api\+)?json\b)/i
11
11
  auth_value_method :json_request_content_type_regexp, /\bapplication\/(?:vnd\.api\+)?json\b/i
12
12
  auth_value_method :json_response_content_type, 'application/json'
13
13
  auth_value_method :json_response_error_status, 400
14
- auth_value_method :json_response_custom_error_status?, false
14
+ auth_value_method :json_response_custom_error_status?, true
15
15
  auth_value_method :json_response_error_key, "error"
16
16
  auth_value_method :json_response_field_error_key, "field-error"
17
- auth_value_method :json_response_success_key, nil
17
+ auth_value_method :json_response_success_key, "success"
18
18
  auth_value_method :jwt_algorithm, "HS256"
19
19
  auth_value_method :jwt_authorization_ignore, /\A(?:Basic|Digest) /
20
20
  auth_value_method :jwt_authorization_remove, /\ABearer:?\s+/
21
- auth_value_method :jwt_check_accept?, false
22
- auth_value_method :jwt_decode_opts, {}
21
+ auth_value_method :jwt_check_accept?, true
22
+ auth_value_method :jwt_decode_opts, {}.freeze
23
23
  auth_value_method :jwt_session_key, nil
24
24
  auth_value_method :jwt_symbolize_deeply?, false
25
- auth_value_method :non_json_request_error_message, 'Only JSON format requests are allowed'
25
+ translatable_method :non_json_request_error_message, 'Only JSON format requests are allowed'
26
26
 
27
27
  auth_value_methods(
28
28
  :only_json?,
@@ -173,6 +173,43 @@ module Rodauth
173
173
  end
174
174
  end
175
175
 
176
+ def before_webauthn_setup_route
177
+ super if defined?(super)
178
+ if use_jwt? && !param_or_nil(webauthn_setup_param)
179
+ cred = new_webauthn_credential
180
+ json_response[webauthn_setup_param] = cred.as_json
181
+ json_response[webauthn_setup_challenge_param] = cred.challenge
182
+ json_response[webauthn_setup_challenge_hmac_param] = compute_hmac(cred.challenge)
183
+ end
184
+ end
185
+
186
+ def before_webauthn_auth_route
187
+ super if defined?(super)
188
+ if use_jwt? && !param_or_nil(webauthn_auth_param)
189
+ cred = webauth_credential_options_for_get
190
+ json_response[webauthn_auth_param] = cred.as_json
191
+ json_response[webauthn_auth_challenge_param] = cred.challenge
192
+ json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
193
+ end
194
+ end
195
+
196
+ def before_webauthn_login_route
197
+ super if defined?(super)
198
+ if use_jwt? && !param_or_nil(webauthn_auth_param) && account_from_login(param(login_param))
199
+ cred = webauth_credential_options_for_get
200
+ json_response[webauthn_auth_param] = cred.as_json
201
+ json_response[webauthn_auth_challenge_param] = cred.challenge
202
+ json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
203
+ end
204
+ end
205
+
206
+ def before_webauthn_remove_route
207
+ super if defined?(super)
208
+ if use_jwt? && !param_or_nil(webauthn_remove_param)
209
+ json_response[webauthn_remove_param] = account_webauthn_usage
210
+ end
211
+ end
212
+
176
213
  def before_otp_setup_route
177
214
  super if defined?(super)
178
215
  if use_jwt? && otp_keys_use_hmac? && !param_or_nil(otp_setup_raw_param)
@@ -204,6 +241,12 @@ module Rodauth
204
241
  value
205
242
  end
206
243
 
244
+ def remove_session_value(key)
245
+ value = super
246
+ set_jwt if use_jwt?
247
+ value
248
+ end
249
+
207
250
  def json_response
208
251
  @json_response ||= {}
209
252
  end