rodauth 2.0.0 → 2.5.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +58 -0
  3. data/README.rdoc +14 -0
  4. data/doc/base.rdoc +2 -0
  5. data/doc/guides/admin_activation.rdoc +46 -0
  6. data/doc/guides/already_authenticated.rdoc +10 -0
  7. data/doc/guides/alternative_login.rdoc +46 -0
  8. data/doc/guides/create_account_programmatically.rdoc +38 -0
  9. data/doc/guides/delay_password.rdoc +25 -0
  10. data/doc/guides/email_only.rdoc +16 -0
  11. data/doc/guides/i18n.rdoc +26 -0
  12. data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
  13. data/doc/guides/links.rdoc +12 -0
  14. data/doc/guides/login_return.rdoc +37 -0
  15. data/doc/guides/password_column.rdoc +25 -0
  16. data/doc/guides/password_confirmation.rdoc +37 -0
  17. data/doc/guides/password_requirements.rdoc +30 -0
  18. data/doc/guides/paths.rdoc +36 -0
  19. data/doc/guides/query_params.rdoc +9 -0
  20. data/doc/guides/redirects.rdoc +17 -0
  21. data/doc/guides/registration_field.rdoc +68 -0
  22. data/doc/guides/require_mfa.rdoc +30 -0
  23. data/doc/guides/reset_password_autologin.rdoc +21 -0
  24. data/doc/guides/status_column.rdoc +28 -0
  25. data/doc/guides/totp_or_recovery.rdoc +16 -0
  26. data/doc/jwt_refresh.rdoc +11 -0
  27. data/doc/login.rdoc +8 -0
  28. data/doc/login_password_requirements_base.rdoc +3 -0
  29. data/doc/otp.rdoc +1 -0
  30. data/doc/password_pepper.rdoc +44 -0
  31. data/doc/release_notes/2.1.0.txt +31 -0
  32. data/doc/release_notes/2.2.0.txt +39 -0
  33. data/doc/release_notes/2.3.0.txt +37 -0
  34. data/doc/release_notes/2.4.0.txt +22 -0
  35. data/doc/release_notes/2.5.0.txt +20 -0
  36. data/doc/verify_login_change.rdoc +1 -0
  37. data/lib/rodauth.rb +5 -5
  38. data/lib/rodauth/features/active_sessions.rb +5 -7
  39. data/lib/rodauth/features/audit_logging.rb +2 -0
  40. data/lib/rodauth/features/base.rb +21 -2
  41. data/lib/rodauth/features/change_password.rb +1 -1
  42. data/lib/rodauth/features/close_account.rb +8 -6
  43. data/lib/rodauth/features/create_account.rb +1 -0
  44. data/lib/rodauth/features/disallow_password_reuse.rb +4 -2
  45. data/lib/rodauth/features/email_auth.rb +2 -2
  46. data/lib/rodauth/features/http_basic_auth.rb +15 -2
  47. data/lib/rodauth/features/jwt.rb +12 -8
  48. data/lib/rodauth/features/jwt_cors.rb +15 -15
  49. data/lib/rodauth/features/jwt_refresh.rb +39 -10
  50. data/lib/rodauth/features/login.rb +23 -12
  51. data/lib/rodauth/features/login_password_requirements_base.rb +9 -4
  52. data/lib/rodauth/features/otp.rb +5 -1
  53. data/lib/rodauth/features/password_complexity.rb +4 -2
  54. data/lib/rodauth/features/password_pepper.rb +45 -0
  55. data/lib/rodauth/features/remember.rb +2 -0
  56. data/lib/rodauth/features/session_expiration.rb +1 -6
  57. data/lib/rodauth/features/single_session.rb +1 -1
  58. data/lib/rodauth/features/sms_codes.rb +0 -1
  59. data/lib/rodauth/features/two_factor_base.rb +4 -4
  60. data/lib/rodauth/features/verify_account.rb +5 -0
  61. data/lib/rodauth/features/verify_account_grace_period.rb +3 -5
  62. data/lib/rodauth/features/verify_login_change.rb +2 -1
  63. data/lib/rodauth/features/webauthn.rb +1 -3
  64. data/lib/rodauth/features/webauthn_login.rb +1 -1
  65. data/lib/rodauth/migrations.rb +16 -5
  66. data/lib/rodauth/version.rb +1 -1
  67. data/templates/password-field.str +1 -1
  68. metadata +37 -5
@@ -0,0 +1,37 @@
1
+ = New Features
2
+
3
+ * Configuration methods have been added for easier validation of
4
+ logins when logins must be valid email addresses (the default):
5
+
6
+ * login_valid_email?(login) can be used for full control of
7
+ determining whether the login is valid.
8
+
9
+ * login_email_regexp can be used to set the regexp used in the
10
+ default login_valid_email? check.
11
+
12
+ * login_not_valid_email_message can be used to set the field
13
+ error message if the login is not a valid email. Previously, this
14
+ value was hardcoded and not translatable.
15
+
16
+ * The {create,drop}_database_authentication_functions now work
17
+ correctly with uuid keys on PostgreSQL. All other parts of
18
+ Rodauth already worked correctly with uuid keys.
19
+
20
+ = Other Improvements
21
+
22
+ * The before_jwt_refresh_route hook is now called before the route
23
+ is taken. Previously, the configuration method had no effect.
24
+
25
+ * rodauth.login can now be used by external code to login the current
26
+ account (the account that rodauth.account returns). This should be
27
+ passed the authentication type string used to login, such as
28
+ password.
29
+
30
+ * The jwt_refresh route now returns an error for requests where a
31
+ valid access token for a logged in session is not provided. You
32
+ can use the jwt_refresh_without_access_token_message and
33
+ jwt_refresh_without_access_token_status configuration methods
34
+ to configure the error response.
35
+
36
+ * The new refresh token is now available to the after_refresh_token
37
+ hook by looking in json_response[jwt_refresh_token_key].
@@ -0,0 +1,22 @@
1
+ = New Features
2
+
3
+ * A password_pepper feature has been added. This allows you to use a
4
+ secret key (called a pepper) to append to passwords before hashing
5
+ and hash checking. Using this approach, if an attacker obtains the
6
+ password hash, it is unusable for cracking unless they can also
7
+ get access to the pepper.
8
+
9
+ The password_pepper feature also supports a list of previous peppers
10
+ that can be used to implement secret rotation and to support
11
+ compatibility with unpeppered passwords.
12
+
13
+ Rodauth by default uses database functions for password hash
14
+ checking on PostgreSQL, MySQL, and Microsoft SQL Server, which in
15
+ general provides more security than a password pepper, but both
16
+ approaches can be used simultaneously.
17
+
18
+ * A session_key_prefix configuration method has been added for
19
+ prefixing the values of all default session keys. This can be
20
+ useful if you are using multiple Rodauth configurations in the same
21
+ application and want to make sure the session keys for the separate
22
+ configurations do not overlap.
@@ -0,0 +1,20 @@
1
+ = New Features
2
+
3
+ * A login_return_to_requested_location_path configuration method has
4
+ been added to the login feature. This controls the path to redirect
5
+ to if using login_return_to_requested_location?. By default, this
6
+ is the same as the fullpath of the request that required login if
7
+ that request was a GET request, and nil if that request was not a
8
+ GET request. Previously, the fullpath of that request was used even
9
+ if it was not a GET request, which caused problems as browsers use a
10
+ GET request for redirects, and it is a bad idea to redirect to a path
11
+ that may not handle GET requests.
12
+
13
+ * A change_login_needs_verification_notice_flash configuration method
14
+ has been added to the verify_login_change feature, for allowing
15
+ translations when using the feature and not using the
16
+ change_login_notice_flash configuration method.
17
+
18
+ = Other Improvements
19
+
20
+ * new_password_label is now translatable.
@@ -14,6 +14,7 @@ control. Depends on the change login and email base features.
14
14
  == Auth Value Methods
15
15
 
16
16
  no_matching_verify_login_change_key_error_flash :: The flash error message to show when an invalid verify login change key is used.
17
+ change_login_needs_verification_notice_flash :: The flash notice to show after changing a login when using this feature, if +change_login_notice_flash+ is not overridden.
17
18
  verify_login_change_additional_form_tags :: HTML fragment containing additional form tags to use on the verify login change form.
18
19
  verify_login_change_autologin? :: Whether to autologin the user after successful login change verification, false by default.
19
20
  verify_login_change_button :: The text to use for the verify login change button.
@@ -119,7 +119,7 @@ module Rodauth
119
119
 
120
120
  define_method(handle_meth) do
121
121
  request.is send(route_meth) do
122
- scope.check_csrf!(check_csrf_opts, &check_csrf_block) if check_csrf?
122
+ check_csrf if check_csrf?
123
123
  before_rodauth
124
124
  send(internal_handle_meth, request)
125
125
  end
@@ -137,7 +137,9 @@ module Rodauth
137
137
  feature.module_eval(&block)
138
138
  configuration.def_configuration_methods(feature)
139
139
 
140
+ # :nocov:
140
141
  if constant
142
+ # :nocov:
141
143
  Rodauth.const_set(constant, feature)
142
144
  Rodauth::FeatureConfiguration.const_set(constant, configuration)
143
145
  end
@@ -336,10 +338,8 @@ module Rodauth
336
338
  end
337
339
 
338
340
  def freeze
339
- if opts[:rodauths]
340
- opts[:rodauths].each_value(&:freeze)
341
- opts[:rodauths].freeze
342
- end
341
+ opts[:rodauths].each_value(&:freeze)
342
+ opts[:rodauths].freeze
343
343
  super
344
344
  end
345
345
  end
@@ -118,14 +118,12 @@ module Rodauth
118
118
  end
119
119
 
120
120
  def before_logout
121
- if request.post?
122
- if param_or_nil(global_logout_param)
123
- remove_all_active_sessions
124
- else
125
- remove_current_session
126
- end
121
+ if param_or_nil(global_logout_param)
122
+ remove_all_active_sessions
123
+ else
124
+ remove_current_session
127
125
  end
128
- super if defined?(super)
126
+ super
129
127
  end
130
128
 
131
129
  def session_inactivity_deadline_condition
@@ -82,7 +82,9 @@ module Rodauth
82
82
 
83
83
  def audit_log_ds
84
84
  ds = db[audit_logging_table]
85
+ # :nocov:
85
86
  if db.database_type == :postgres
87
+ # :nocov:
86
88
  # For PostgreSQL, use RETURNING NULL. This allows the feature
87
89
  # to be used with INSERT but not SELECT permissions on the
88
90
  # table, useful for audit logging where the database user
@@ -47,6 +47,7 @@ module Rodauth
47
47
  session_key :authenticated_by_session_key, :authenticated_by
48
48
  session_key :autologin_type_session_key, :autologin_type
49
49
  auth_value_method :prefix, ''
50
+ auth_value_method :session_key_prefix, nil
50
51
  auth_value_method :require_bcrypt?, true
51
52
  auth_value_method :mark_input_fields_as_required?, true
52
53
  auth_value_method :mark_input_fields_with_autocomplete?, true
@@ -82,6 +83,7 @@ module Rodauth
82
83
  :already_logged_in,
83
84
  :authenticated?,
84
85
  :autocomplete_for_field?,
86
+ :check_csrf,
85
87
  :clear_session,
86
88
  :csrf_tag,
87
89
  :function_name,
@@ -254,6 +256,10 @@ module Rodauth
254
256
  Sequel::DATABASES.first
255
257
  end
256
258
 
259
+ def password_field_autocomplete_value
260
+ @password_field_autocomplete_value || 'current-password'
261
+ end
262
+
257
263
  # If the account_password_hash_column is set, the password hash is verified in
258
264
  # ruby, it will not use a database function to do so, it will check the password
259
265
  # hash using bcrypt.
@@ -333,6 +339,10 @@ module Rodauth
333
339
  @account = _account_from_session
334
340
  end
335
341
 
342
+ def check_csrf
343
+ scope.check_csrf!(check_csrf_opts, &check_csrf_block)
344
+ end
345
+
336
346
  def csrf_tag(path=request.path)
337
347
  return unless scope.respond_to?(:csrf_tag)
338
348
 
@@ -384,9 +394,9 @@ module Rodauth
384
394
  def password_match?(password)
385
395
  if hash = get_password_hash
386
396
  if account_password_hash_column || !use_database_authentication_functions?
387
- BCrypt::Password.new(hash) == password
397
+ password_hash_match?(hash, password)
388
398
  else
389
- db.get(Sequel.function(function_name(:rodauth_valid_password_hash), account_id, BCrypt::Engine.hash_secret(password, hash)))
399
+ database_function_password_match?(:rodauth_valid_password_hash, account_id, password, hash)
390
400
  end
391
401
  end
392
402
  end
@@ -449,6 +459,14 @@ module Rodauth
449
459
 
450
460
  private
451
461
 
462
+ def database_function_password_match?(name, hash_id, password, salt)
463
+ db.get(Sequel.function(function_name(name), hash_id, BCrypt::Engine.hash_secret(password, salt)))
464
+ end
465
+
466
+ def password_hash_match?(hash, password)
467
+ BCrypt::Password.new(hash) == password
468
+ end
469
+
452
470
  def convert_token_key(key)
453
471
  if key && hmac_secret
454
472
  compute_hmac(key)
@@ -484,6 +502,7 @@ module Rodauth
484
502
  end
485
503
 
486
504
  def convert_session_key(key)
505
+ key = "#{session_key_prefix}#{key}".to_sym if session_key_prefix
487
506
  scope.opts[:sessions_convert_symbols] ? key.to_s : key
488
507
  end
489
508
 
@@ -14,7 +14,7 @@ module Rodauth
14
14
  button 'Change Password'
15
15
  redirect
16
16
 
17
- auth_value_method :new_password_label, 'New Password'
17
+ translatable_method :new_password_label, 'New Password'
18
18
  auth_value_method :new_password_param, 'new-password'
19
19
 
20
20
  auth_value_methods(
@@ -33,7 +33,11 @@ module Rodauth
33
33
  end
34
34
 
35
35
  r.post do
36
- if !close_account_requires_password? || password_match?(param(password_param))
36
+ catch_error do
37
+ if close_account_requires_password? && !password_match?(param(password_param))
38
+ throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
39
+ end
40
+
37
41
  transaction do
38
42
  before_close_account
39
43
  close_account
@@ -46,12 +50,10 @@ module Rodauth
46
50
 
47
51
  set_notice_flash close_account_notice_flash
48
52
  redirect close_account_redirect
49
- else
50
- set_response_error_status(invalid_password_error_status)
51
- set_field_error(password_param, invalid_password_message)
52
- set_error_flash close_account_error_flash
53
- close_account_view
54
53
  end
54
+
55
+ set_error_flash close_account_error_flash
56
+ close_account_view
55
57
  end
56
58
  end
57
59
 
@@ -30,6 +30,7 @@ module Rodauth
30
30
  route do |r|
31
31
  check_already_logged_in
32
32
  before_create_account_route
33
+ @password_field_autocomplete_value = 'new-password'
33
34
 
34
35
  r.get do
35
36
  create_account_view
@@ -51,11 +51,13 @@ module Rodauth
51
51
  return true if salts.empty?
52
52
 
53
53
  salts.any? do |hash_id, salt|
54
- db.get(Sequel.function(function_name(:rodauth_previous_password_hash_match), hash_id, BCrypt::Engine.hash_secret(password, salt)))
54
+ database_function_password_match?(:rodauth_previous_password_hash_match, hash_id, password, salt)
55
55
  end
56
56
  else
57
57
  # :nocov:
58
- previous_password_ds.select_map(previous_password_hash_column).any?{|hash| BCrypt::Password.new(hash) == password}
58
+ previous_password_ds.select_map(previous_password_hash_column).any? do |hash|
59
+ password_hash_match?(hash, password)
60
+ end
59
61
  # :nocov:
60
62
  end
61
63
 
@@ -94,7 +94,7 @@ module Rodauth
94
94
  redirect email_auth_email_sent_redirect
95
95
  end
96
96
 
97
- _login('email_auth')
97
+ login('email_auth')
98
98
  end
99
99
  end
100
100
 
@@ -215,7 +215,7 @@ module Rodauth
215
215
  # that allows login access to the account becomes a
216
216
  # security liability, and it is best to remove it.
217
217
  remove_email_auth_key
218
- super if defined?(super)
218
+ super
219
219
  end
220
220
 
221
221
  def after_close_account
@@ -5,11 +5,20 @@ 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
+ def logged_in?
9
+ ret = super
10
+
11
+ if !ret && !defined?(@checked_http_basic_auth)
12
+ http_basic_auth
13
+ ret = super
14
+ end
15
+
16
+ ret
17
+ end
18
+
8
19
  def require_login
9
20
  if require_http_basic_auth?
10
21
  require_http_basic_auth
11
- elsif !logged_in?
12
- http_basic_auth
13
22
  end
14
23
 
15
24
  super
@@ -23,6 +32,9 @@ module Rodauth
23
32
  end
24
33
 
25
34
  def http_basic_auth
35
+ return @checked_http_basic_auth if defined?(@checked_http_basic_auth)
36
+
37
+ @checked_http_basic_auth = nil
26
38
  return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
27
39
 
28
40
  username, password = token.unpack("m*").first.split(/:/, 2)
@@ -50,6 +62,7 @@ module Rodauth
50
62
  after_login
51
63
  end
52
64
 
65
+ @checked_http_basic_auth = true
53
66
  return true
54
67
  end
55
68
 
@@ -50,7 +50,7 @@ module Rodauth
50
50
  json_response[json_response_error_key] = invalid_jwt_format_error_message
51
51
  response.status ||= json_response_error_status
52
52
  response['Content-Type'] ||= json_response_content_type
53
- response.write(request.send(:convert_to_json, json_response))
53
+ response.write(_json_response_body(json_response))
54
54
  request.halt
55
55
  end
56
56
 
@@ -138,15 +138,25 @@ module Rodauth
138
138
  !!(jwt_token && jwt_payload)
139
139
  end
140
140
 
141
+ def view(page, title)
142
+ return super unless use_jwt?
143
+ return_json_response
144
+ end
145
+
141
146
  private
142
147
 
148
+ def check_csrf?
149
+ return false if use_jwt?
150
+ super
151
+ end
152
+
143
153
  def before_rodauth
144
154
  if json_request?
145
155
  if jwt_check_accept? && (accept = request.env['HTTP_ACCEPT']) && accept !~ json_accept_regexp
146
156
  response.status = 406
147
157
  json_response[json_response_error_key] = json_not_accepted_error_message
148
158
  response['Content-Type'] ||= json_response_content_type
149
- response.write(request.send(:convert_to_json, json_response))
159
+ response.write(_json_response_body(json_response))
150
160
  request.halt
151
161
  end
152
162
 
@@ -251,12 +261,6 @@ module Rodauth
251
261
  @json_response ||= {}
252
262
  end
253
263
 
254
- def _view(meth, page)
255
- return super unless use_jwt?
256
- return super if meth == :render
257
- return_json_response
258
- end
259
-
260
264
  def _json_response_body(hash)
261
265
  request.send(:convert_to_json, hash)
262
266
  end
@@ -13,27 +13,27 @@ module Rodauth
13
13
  auth_methods(:jwt_cors_allow?)
14
14
 
15
15
  def jwt_cors_allow?
16
- if origin = request.env['HTTP_ORIGIN']
17
- case allowed = jwt_cors_allow_origin
18
- when String
19
- timing_safe_eql?(origin, allowed)
20
- when Array
21
- allowed.any?{|s| timing_safe_eql?(origin, s)}
22
- when Regexp
23
- allowed =~ origin
24
- when true
25
- true
26
- else
27
- false
28
- end
16
+ return false unless origin = request.env['HTTP_ORIGIN']
17
+
18
+ case allowed = jwt_cors_allow_origin
19
+ when String
20
+ timing_safe_eql?(origin, allowed)
21
+ when Array
22
+ allowed.any?{|s| timing_safe_eql?(origin, s)}
23
+ when Regexp
24
+ allowed =~ origin
25
+ when true
26
+ true
27
+ else
28
+ false
29
29
  end
30
30
  end
31
31
 
32
32
  private
33
33
 
34
34
  def before_rodauth
35
- if (origin = request.env['HTTP_ORIGIN']) && jwt_cors_allow?
36
- response['Access-Control-Allow-Origin'] = origin
35
+ if jwt_cors_allow?
36
+ response['Access-Control-Allow-Origin'] = request.env['HTTP_ORIGIN']
37
37
 
38
38
  # Handle CORS preflight request
39
39
  if request.request_method == 'OPTIONS'
@@ -19,23 +19,29 @@ module Rodauth
19
19
  auth_value_method :jwt_refresh_token_key_column, :key
20
20
  auth_value_method :jwt_refresh_token_key_param, 'refresh_token'
21
21
  auth_value_method :jwt_refresh_token_table, :account_jwt_refresh_keys
22
+ translatable_method :jwt_refresh_without_access_token_message, 'no JWT access token provided during refresh'
23
+ auth_value_method :jwt_refresh_without_access_token_status, 401
22
24
 
23
25
  auth_private_methods(
24
26
  :account_from_refresh_token
25
27
  )
26
28
 
27
29
  route do |r|
30
+ before_jwt_refresh_route
31
+
28
32
  r.post do
29
- if (refresh_token = param_or_nil(jwt_refresh_token_key_param)) && account_from_refresh_token(refresh_token)
30
- formatted_token = nil
33
+ if !session_value
34
+ response.status ||= jwt_refresh_without_access_token_status
35
+ json_response[json_response_error_key] = jwt_refresh_without_access_token_message
36
+ elsif (refresh_token = param_or_nil(jwt_refresh_token_key_param)) && account_from_refresh_token(refresh_token)
31
37
  transaction do
32
38
  before_refresh_token
33
39
  formatted_token = generate_refresh_token
34
40
  remove_jwt_refresh_token_key(refresh_token)
41
+ json_response[jwt_refresh_token_key] = formatted_token
42
+ json_response[jwt_access_token_key] = session_jwt
35
43
  after_refresh_token
36
44
  end
37
- json_response[jwt_refresh_token_key] = formatted_token
38
- json_response[jwt_access_token_key] = session_jwt
39
45
  else
40
46
  json_response[json_response_error_key] = jwt_refresh_invalid_token_message
41
47
  response.status ||= json_response_error_status
@@ -80,14 +86,10 @@ module Rodauth
80
86
  private
81
87
 
82
88
  def _account_from_refresh_token(token)
83
- id, token = split_token(token)
84
- return unless id && token
85
-
86
- token_id, key = split_token(token)
87
- return unless token_id && key
89
+ id, token_id, key = _account_refresh_token_split(token)
88
90
 
91
+ return unless key
89
92
  return unless actual = get_active_refresh_token(id, token_id)
90
-
91
93
  return unless timing_safe_eql?(key, convert_token_key(actual))
92
94
 
93
95
  ds = account_ds(id)
@@ -95,6 +97,16 @@ module Rodauth
95
97
  ds.first
96
98
  end
97
99
 
100
+ def _account_refresh_token_split(token)
101
+ id, token = split_token(token)
102
+ return unless id && token
103
+
104
+ token_id, key = split_token(token)
105
+ return unless token_id && key
106
+
107
+ [id, token_id, key]
108
+ end
109
+
98
110
  def get_active_refresh_token(account_id, token_id)
99
111
  jwt_refresh_token_account_ds(account_id).
100
112
  where(Sequel::CURRENT_TIMESTAMP > jwt_refresh_token_deadline_column).
@@ -134,6 +146,23 @@ module Rodauth
134
146
  hash
135
147
  end
136
148
 
149
+ def before_logout
150
+ if token = param_or_nil(jwt_refresh_token_key_param)
151
+ if token == 'all'
152
+ jwt_refresh_token_account_ds(session_value).delete
153
+ else
154
+ id, token_id, key = _account_refresh_token_split(token)
155
+
156
+ if id && token_id && key && (actual = get_active_refresh_token(session_value, token_id)) && timing_safe_eql?(key, convert_token_key(actual))
157
+ jwt_refresh_token_account_ds(id).
158
+ where(jwt_refresh_token_id_column=>token_id).
159
+ delete
160
+ end
161
+ end
162
+ end
163
+ super if defined?(super)
164
+ end
165
+
137
166
  def after_close_account
138
167
  jwt_refresh_token_account_ds(account_id).delete
139
168
  super if defined?(super)