rodauth 2.6.0 → 2.11.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/CHANGELOG +42 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +21 -6
- data/doc/argon2.rdoc +49 -0
- data/doc/base.rdoc +1 -1
- data/doc/change_login.rdoc +1 -0
- data/doc/guides/migrate_password_hash_algorithm.rdoc +15 -0
- data/doc/json.rdoc +47 -0
- data/doc/jwt.rdoc +1 -28
- data/doc/jwt_refresh.rdoc +2 -0
- data/doc/login_password_requirements_base.rdoc +2 -1
- data/doc/recovery_codes.rdoc +2 -1
- data/doc/release_notes/2.10.0.txt +47 -0
- data/doc/release_notes/2.11.0.txt +31 -0
- data/doc/release_notes/2.7.0.txt +33 -0
- data/doc/release_notes/2.8.0.txt +20 -0
- data/doc/release_notes/2.9.0.txt +21 -0
- data/doc/remember.rdoc +1 -1
- data/lib/rodauth.rb +17 -4
- data/lib/rodauth/features/argon2.rb +69 -0
- data/lib/rodauth/features/base.rb +6 -2
- data/lib/rodauth/features/change_login.rb +2 -1
- data/lib/rodauth/features/disallow_password_reuse.rb +20 -7
- data/lib/rodauth/features/email_base.rb +5 -2
- data/lib/rodauth/features/json.rb +189 -0
- data/lib/rodauth/features/jwt.rb +19 -171
- data/lib/rodauth/features/jwt_refresh.rb +23 -10
- data/lib/rodauth/features/login_password_requirements_base.rb +6 -1
- data/lib/rodauth/features/otp.rb +0 -2
- data/lib/rodauth/features/recovery_codes.rb +22 -1
- data/lib/rodauth/features/remember.rb +6 -1
- data/lib/rodauth/features/reset_password.rb +1 -0
- data/lib/rodauth/features/update_password_hash.rb +1 -1
- data/lib/rodauth/features/verify_account.rb +0 -1
- data/lib/rodauth/features/webauthn_verify_account.rb +1 -1
- data/lib/rodauth/migrations.rb +31 -5
- data/lib/rodauth/version.rb +1 -1
- metadata +55 -24
@@ -0,0 +1,33 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* An auto_remove_recovery_codes? configuration method has been added
|
4
|
+
to the recovery_codes feature. This will automatically remove
|
5
|
+
recovery codes when the last multifactor authentication type other
|
6
|
+
than the recovery codes has been removed.
|
7
|
+
|
8
|
+
* The jwt_access_expired_status and expired_jwt_access_token_message
|
9
|
+
configuration methods have been added to the jwt_refresh feature,
|
10
|
+
for supporting custom statuses and messages for expired tokens.
|
11
|
+
|
12
|
+
= Other Improvements
|
13
|
+
|
14
|
+
* Rodauth will no longer attempt to require a feature that has
|
15
|
+
already been required. Related to this is you can now use a
|
16
|
+
a custom Rodauth feature without a rodauth/features/*.rb file
|
17
|
+
in the Ruby library path, as long as you load the feature
|
18
|
+
manually.
|
19
|
+
|
20
|
+
* Rodauth now avoids method redefinition warnings in verbose
|
21
|
+
warning mode. As Ruby 3 is dropping uninitialized instance
|
22
|
+
variable warnings, Rodauth will be verbose warning free in
|
23
|
+
Ruby 3.
|
24
|
+
|
25
|
+
= Backwards Compatibility
|
26
|
+
|
27
|
+
* The default remember cookie path is now set to '/'. This fixes
|
28
|
+
usage in the case where rodauth is loaded under a subpath of the
|
29
|
+
application (which is not the default behavior). Unfortunately,
|
30
|
+
this change can negatively affect cases where multiple rodauth
|
31
|
+
configurations are used in separate paths on the same domain.
|
32
|
+
In these cases, you should now use remember_cookie_options and
|
33
|
+
include a :path option.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
= Improvements
|
2
|
+
|
3
|
+
* HttpOnly is now set by default on the remember cookie, so it is no
|
4
|
+
longer accessible from Javascript. This is a more secure approach
|
5
|
+
that makes applications using Rodauth's remember feature less
|
6
|
+
vulnerable in case they are subject to a separate XSS attack.
|
7
|
+
|
8
|
+
* When using the jwt feature, rodauth.clear_session now clears the
|
9
|
+
JWT session even when the Roda sessions plugin was in use. In most
|
10
|
+
cases, the jwt feature is not used with the Roda sessions plugin,
|
11
|
+
but in cases where the same application serves as both an JSON API
|
12
|
+
and as a HTML site, it is possible the two may be used together.
|
13
|
+
|
14
|
+
= Backwards Compatibility
|
15
|
+
|
16
|
+
* As the default remember cookie :httponly setting is now set to true,
|
17
|
+
applications using Rodauth that expected to be able to access the
|
18
|
+
remember cookie from Javascript will no longer work by default.
|
19
|
+
In these cases, you should now use remember_cookie_options and
|
20
|
+
include a :httponly=>false option.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A json feature has been extracted from the existing jwt feature.
|
4
|
+
This feature allows for the same JSON API previously supported
|
5
|
+
by the JWT feature, but stores the session information in the
|
6
|
+
Rack session instead of in a separate JWT. This makes it
|
7
|
+
significantly easier to have certain pages use the JSON API,
|
8
|
+
and other pages the HTML forms.
|
9
|
+
|
10
|
+
= Other Improvements
|
11
|
+
|
12
|
+
* If the remember cookie is created in an SSL request, the Secure
|
13
|
+
flag is added by default, so the cookie will not be transmitted
|
14
|
+
in non-SSL requests.
|
15
|
+
|
16
|
+
= Backwards Compatibility
|
17
|
+
|
18
|
+
* Rodauth configurations that use the remember feature and support
|
19
|
+
requests over both http and https and want to have the remember
|
20
|
+
cookie transmitted over both should now include :secure=>false in
|
21
|
+
remember_cookie_options.
|
data/doc/remember.rdoc
CHANGED
@@ -35,7 +35,7 @@ raw_remember_token_deadline :: A deadline before which to allow a raw remember t
|
|
35
35
|
remember_additional_form_tags :: HTML fragment containing additional form tags to use on the change remember setting form.
|
36
36
|
remember_button :: The text to use for the change remember settings button.
|
37
37
|
remember_cookie_key :: The cookie name to use for the remember token.
|
38
|
-
remember_cookie_options :: Any options to set for the remember cookie.
|
38
|
+
remember_cookie_options :: Any options to set for the remember cookie. By default, the `:path` cookie option is set to `/` and `:httponly` is set to `true`. Also, `:secure` is set to `true` by default if the current request is an HTTPS request.
|
39
39
|
remember_deadline_column :: The column name in the +remember_table+ storing the deadline after which the token will be ignored.
|
40
40
|
remember_deadline_interval :: The amount of time for which to remember accounts, 14 days by default. Only used if +set_deadline_values?+ is true.
|
41
41
|
remember_disable_label :: The label for disabling remembering.
|
data/lib/rodauth.rb
CHANGED
@@ -39,14 +39,14 @@ module Rodauth
|
|
39
39
|
else
|
40
40
|
json_opt != :only
|
41
41
|
end
|
42
|
-
auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= Class.new(Auth)
|
42
|
+
auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= opts[:auth_class] || Class.new(Auth)
|
43
43
|
if !auth_class.roda_class
|
44
44
|
auth_class.roda_class = app
|
45
45
|
elsif auth_class.roda_class != app
|
46
46
|
auth_class = app.opts[:rodauths][opts[:name]] = Class.new(auth_class)
|
47
47
|
auth_class.roda_class = app
|
48
48
|
end
|
49
|
-
auth_class.configure(&block)
|
49
|
+
auth_class.configure(&block) if block
|
50
50
|
end
|
51
51
|
|
52
52
|
FEATURES = {}
|
@@ -66,6 +66,7 @@ module Rodauth
|
|
66
66
|
define_method(meth) do |&block|
|
67
67
|
@auth.send(:define_method, meth, &block)
|
68
68
|
@auth.send(:private, meth) if priv
|
69
|
+
@auth.send(:alias_method, meth, meth)
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
@@ -74,6 +75,7 @@ module Rodauth
|
|
74
75
|
define_method(meth) do |&block|
|
75
76
|
@auth.send(:define_method, umeth, &block)
|
76
77
|
@auth.send(:private, umeth)
|
78
|
+
@auth.send(:alias_method, umeth, umeth)
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
@@ -82,6 +84,7 @@ module Rodauth
|
|
82
84
|
block ||= proc{v}
|
83
85
|
@auth.send(:define_method, meth, &block)
|
84
86
|
@auth.send(:private, meth) if priv
|
87
|
+
@auth.send(:alias_method, meth, meth)
|
85
88
|
end
|
86
89
|
end
|
87
90
|
end
|
@@ -240,6 +243,7 @@ module Rodauth
|
|
240
243
|
instance_variable_set(iv, send(umeth))
|
241
244
|
end
|
242
245
|
end
|
246
|
+
alias_method(meth, meth)
|
243
247
|
auth_private_methods(meth)
|
244
248
|
end
|
245
249
|
|
@@ -264,11 +268,12 @@ module Rodauth
|
|
264
268
|
@features = []
|
265
269
|
@routes = []
|
266
270
|
@route_hash = {}
|
271
|
+
@configuration = Configuration.new(self)
|
267
272
|
end
|
268
273
|
end
|
269
274
|
|
270
275
|
def self.configure(&block)
|
271
|
-
|
276
|
+
@configuration.apply(&block)
|
272
277
|
end
|
273
278
|
|
274
279
|
def self.freeze
|
@@ -284,6 +289,14 @@ module Rodauth
|
|
284
289
|
|
285
290
|
def initialize(auth, &block)
|
286
291
|
@auth = auth
|
292
|
+
# :nocov:
|
293
|
+
# Only for backwards compatibility
|
294
|
+
# RODAUTH3: Remove
|
295
|
+
apply(&block) if block
|
296
|
+
# :nocov:
|
297
|
+
end
|
298
|
+
|
299
|
+
def apply(&block)
|
287
300
|
load_feature(:base)
|
288
301
|
instance_exec(&block)
|
289
302
|
auth.allocate.post_configure
|
@@ -300,7 +313,7 @@ module Rodauth
|
|
300
313
|
private
|
301
314
|
|
302
315
|
def load_feature(feature_name)
|
303
|
-
require "rodauth/features/#{feature_name}"
|
316
|
+
require "rodauth/features/#{feature_name}" unless FEATURES[feature_name]
|
304
317
|
feature = FEATURES[feature_name]
|
305
318
|
enable(*feature.dependencies)
|
306
319
|
extend feature.configuration
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'argon2'
|
4
|
+
|
5
|
+
# :nocov:
|
6
|
+
if !defined?(Argon2::VERSION) || Argon2::VERSION < '2'
|
7
|
+
raise LoadError, "argon2 version 1.x not supported as it does not support argon2id hashes"
|
8
|
+
end
|
9
|
+
# :nocov:
|
10
|
+
|
11
|
+
module Rodauth
|
12
|
+
Feature.define(:argon2, :Argon2) do
|
13
|
+
depends :login_password_requirements_base
|
14
|
+
|
15
|
+
auth_value_method :use_argon2?, true
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def password_hash_cost
|
20
|
+
return super unless use_argon2?
|
21
|
+
argon2_hash_cost
|
22
|
+
end
|
23
|
+
|
24
|
+
def password_hash(password)
|
25
|
+
return super unless use_argon2?
|
26
|
+
::Argon2::Password.new(password_hash_cost).create(password)
|
27
|
+
end
|
28
|
+
|
29
|
+
def password_hash_match?(hash, password)
|
30
|
+
return super unless argon2_hash_algorithm?(hash)
|
31
|
+
argon2_password_hash_match?(hash, password)
|
32
|
+
end
|
33
|
+
|
34
|
+
def password_hash_using_salt(password, salt)
|
35
|
+
return super unless argon2_hash_algorithm?(salt)
|
36
|
+
|
37
|
+
argon2_params = Hash[extract_password_hash_cost(salt)]
|
38
|
+
argon2_params[:salt_do_not_supply] = Base64.decode64(salt.split('$').last)
|
39
|
+
::Argon2::Password.new(argon2_params).create(password)
|
40
|
+
end
|
41
|
+
|
42
|
+
def extract_password_hash_cost(hash)
|
43
|
+
return super unless argon2_hash_algorithm?(hash )
|
44
|
+
|
45
|
+
/\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+)/ =~ hash
|
46
|
+
{ t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i }
|
47
|
+
end
|
48
|
+
|
49
|
+
if ENV['RACK_ENV'] == 'test'
|
50
|
+
def argon2_hash_cost
|
51
|
+
{t_cost: 1, m_cost: 3}
|
52
|
+
end
|
53
|
+
# :nocov:
|
54
|
+
else
|
55
|
+
def argon2_hash_cost
|
56
|
+
{t_cost: 2, m_cost: 16}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# :nocov:
|
60
|
+
|
61
|
+
def argon2_hash_algorithm?(hash)
|
62
|
+
hash.start_with?('$argon2id$')
|
63
|
+
end
|
64
|
+
|
65
|
+
def argon2_password_hash_match?(hash, password)
|
66
|
+
::Argon2::Password.verify_password(password, hash)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -102,7 +102,6 @@ module Rodauth
|
|
102
102
|
:set_redirect_error_flash,
|
103
103
|
:set_title,
|
104
104
|
:translate,
|
105
|
-
:unverified_account_message,
|
106
105
|
:update_session
|
107
106
|
)
|
108
107
|
|
@@ -261,6 +260,7 @@ module Rodauth
|
|
261
260
|
@password_field_autocomplete_value || 'current-password'
|
262
261
|
end
|
263
262
|
|
263
|
+
alias account_password_hash_column account_password_hash_column
|
264
264
|
# If the account_password_hash_column is set, the password hash is verified in
|
265
265
|
# ruby, it will not use a database function to do so, it will check the password
|
266
266
|
# hash using bcrypt.
|
@@ -465,7 +465,7 @@ module Rodauth
|
|
465
465
|
end
|
466
466
|
|
467
467
|
def database_function_password_match?(name, hash_id, password, salt)
|
468
|
-
db.get(Sequel.function(function_name(name), hash_id,
|
468
|
+
db.get(Sequel.function(function_name(name), hash_id, password_hash_using_salt(password, salt)))
|
469
469
|
end
|
470
470
|
|
471
471
|
def password_hash_match?(hash, password)
|
@@ -593,6 +593,10 @@ module Rodauth
|
|
593
593
|
@has_password = !!get_password_hash
|
594
594
|
end
|
595
595
|
|
596
|
+
def password_hash_using_salt(password, salt)
|
597
|
+
BCrypt::Engine.hash_secret(password, salt)
|
598
|
+
end
|
599
|
+
|
596
600
|
# Get the password hash for the user. When using database authentication functions,
|
597
601
|
# note that only the salt is returned.
|
598
602
|
def get_password_hash
|
@@ -6,6 +6,7 @@ module Rodauth
|
|
6
6
|
|
7
7
|
notice_flash 'Your login has been changed'
|
8
8
|
error_flash 'There was an error changing your login'
|
9
|
+
translatable_method :same_as_current_login_message, 'same as current login'
|
9
10
|
loaded_templates %w'change-login login-field login-confirm-field password-field'
|
10
11
|
view 'change-login', 'Change Login'
|
11
12
|
after
|
@@ -64,7 +65,7 @@ module Rodauth
|
|
64
65
|
|
65
66
|
def change_login(login)
|
66
67
|
if account_ds.get(login_column).downcase == login.downcase
|
67
|
-
@login_requirement_message =
|
68
|
+
@login_requirement_message = same_as_current_login_message
|
68
69
|
return false
|
69
70
|
end
|
70
71
|
|
@@ -24,13 +24,16 @@ module Rodauth
|
|
24
24
|
|
25
25
|
def add_previous_password_hash(hash)
|
26
26
|
ds = previous_password_ds
|
27
|
-
keep_before = ds.reverse(previous_password_id_column).
|
28
|
-
limit(nil, previous_passwords_to_check).
|
29
|
-
get(previous_password_id_column)
|
30
27
|
|
31
|
-
|
32
|
-
ds.
|
33
|
-
|
28
|
+
unless @dont_check_previous_password
|
29
|
+
keep_before = ds.reverse(previous_password_id_column).
|
30
|
+
limit(nil, previous_passwords_to_check).
|
31
|
+
get(previous_password_id_column)
|
32
|
+
|
33
|
+
if keep_before
|
34
|
+
ds.where(Sequel.expr(previous_password_id_column) <= keep_before).
|
35
|
+
delete
|
36
|
+
end
|
34
37
|
end
|
35
38
|
|
36
39
|
# This should never raise uniqueness violations, as it uses a serial primary key
|
@@ -39,7 +42,7 @@ module Rodauth
|
|
39
42
|
|
40
43
|
def password_meets_requirements?(password)
|
41
44
|
super &&
|
42
|
-
password_doesnt_match_previous_password?(password)
|
45
|
+
(@dont_check_previous_password || password_doesnt_match_previous_password?(password))
|
43
46
|
end
|
44
47
|
|
45
48
|
private
|
@@ -71,6 +74,16 @@ module Rodauth
|
|
71
74
|
previous_password_ds.delete
|
72
75
|
end
|
73
76
|
|
77
|
+
def before_create_account_route
|
78
|
+
super if defined?(super)
|
79
|
+
@dont_check_previous_password = true
|
80
|
+
end
|
81
|
+
|
82
|
+
def before_verify_account_route
|
83
|
+
super if defined?(super)
|
84
|
+
@dont_check_previous_password = true
|
85
|
+
end
|
86
|
+
|
74
87
|
def after_create_account
|
75
88
|
if account_password_hash_column && !(respond_to?(:verify_account_set_password?) && verify_account_set_password?)
|
76
89
|
add_previous_password_hash(password_hash(param(password_param)))
|
@@ -51,7 +51,11 @@ module Rodauth
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def token_link(route, param, key)
|
54
|
-
route_url(route, param =>
|
54
|
+
route_url(route, param => token_param_value(key))
|
55
|
+
end
|
56
|
+
|
57
|
+
def token_param_value(key)
|
58
|
+
"#{account_id}#{token_separator}#{convert_email_token_key(key)}"
|
55
59
|
end
|
56
60
|
|
57
61
|
def convert_email_token_key(key)
|
@@ -71,7 +75,6 @@ module Rodauth
|
|
71
75
|
return
|
72
76
|
end
|
73
77
|
end
|
74
|
-
|
75
78
|
ds = account_ds(id)
|
76
79
|
ds = ds.where(account_status_column=>status_id) if status_id && !skip_status_checks?
|
77
80
|
ds.first
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
Feature.define(:json, :Json) do
|
5
|
+
translatable_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
|
6
|
+
translatable_method :json_non_post_error_message, 'non-POST method used in JSON API'
|
7
|
+
auth_value_method :json_accept_regexp, /(?:(?:\*|\bapplication)\/\*|\bapplication\/(?:vnd\.api\+)?json\b)/i
|
8
|
+
auth_value_method :json_check_accept?, true
|
9
|
+
auth_value_method :json_request_content_type_regexp, /\bapplication\/(?:vnd\.api\+)?json\b/i
|
10
|
+
auth_value_method :json_response_content_type, 'application/json'
|
11
|
+
auth_value_method :json_response_custom_error_status?, true
|
12
|
+
auth_value_method :json_response_error_status, 400
|
13
|
+
auth_value_method :json_response_error_key, "error"
|
14
|
+
auth_value_method :json_response_field_error_key, "field-error"
|
15
|
+
auth_value_method :json_response_success_key, "success"
|
16
|
+
translatable_method :non_json_request_error_message, 'Only JSON format requests are allowed'
|
17
|
+
|
18
|
+
auth_value_methods(
|
19
|
+
:only_json?,
|
20
|
+
:use_json?,
|
21
|
+
)
|
22
|
+
|
23
|
+
auth_methods(
|
24
|
+
:json_request?,
|
25
|
+
)
|
26
|
+
|
27
|
+
auth_private_methods :json_response_body
|
28
|
+
|
29
|
+
def set_field_error(field, message)
|
30
|
+
return super unless use_json?
|
31
|
+
json_response[json_response_field_error_key] = [field, message]
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_error_flash(message)
|
35
|
+
return super unless use_json?
|
36
|
+
json_response[json_response_error_key] = message
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_redirect_error_flash(message)
|
40
|
+
return super unless use_json?
|
41
|
+
json_response[json_response_error_key] = message
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_notice_flash(message)
|
45
|
+
return super unless use_json?
|
46
|
+
json_response[json_response_success_key] = message if include_success_messages?
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_notice_now_flash(message)
|
50
|
+
return super unless use_json?
|
51
|
+
json_response[json_response_success_key] = message if include_success_messages?
|
52
|
+
end
|
53
|
+
|
54
|
+
def json_request?
|
55
|
+
return @json_request if defined?(@json_request)
|
56
|
+
@json_request = request.content_type =~ json_request_content_type_regexp
|
57
|
+
end
|
58
|
+
|
59
|
+
def use_json?
|
60
|
+
json_request? || only_json?
|
61
|
+
end
|
62
|
+
|
63
|
+
def view(page, title)
|
64
|
+
return super unless use_json?
|
65
|
+
return_json_response
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def before_view_recovery_codes
|
71
|
+
super if defined?(super)
|
72
|
+
if use_json?
|
73
|
+
json_response[:codes] = recovery_codes
|
74
|
+
json_response[json_response_success_key] ||= "" if include_success_messages?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def before_webauthn_setup_route
|
79
|
+
super if defined?(super)
|
80
|
+
if use_json? && !param_or_nil(webauthn_setup_param)
|
81
|
+
cred = new_webauthn_credential
|
82
|
+
json_response[webauthn_setup_param] = cred.as_json
|
83
|
+
json_response[webauthn_setup_challenge_param] = cred.challenge
|
84
|
+
json_response[webauthn_setup_challenge_hmac_param] = compute_hmac(cred.challenge)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def before_webauthn_auth_route
|
89
|
+
super if defined?(super)
|
90
|
+
if use_json? && !param_or_nil(webauthn_auth_param)
|
91
|
+
cred = webauth_credential_options_for_get
|
92
|
+
json_response[webauthn_auth_param] = cred.as_json
|
93
|
+
json_response[webauthn_auth_challenge_param] = cred.challenge
|
94
|
+
json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def before_webauthn_login_route
|
99
|
+
super if defined?(super)
|
100
|
+
if use_json? && !param_or_nil(webauthn_auth_param) && account_from_login(param(login_param))
|
101
|
+
cred = webauth_credential_options_for_get
|
102
|
+
json_response[webauthn_auth_param] = cred.as_json
|
103
|
+
json_response[webauthn_auth_challenge_param] = cred.challenge
|
104
|
+
json_response[webauthn_auth_challenge_hmac_param] = compute_hmac(cred.challenge)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def before_webauthn_remove_route
|
109
|
+
super if defined?(super)
|
110
|
+
if use_json? && !param_or_nil(webauthn_remove_param)
|
111
|
+
json_response[webauthn_remove_param] = account_webauthn_usage
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def before_otp_setup_route
|
116
|
+
super if defined?(super)
|
117
|
+
if use_json? && otp_keys_use_hmac? && !param_or_nil(otp_setup_raw_param)
|
118
|
+
_otp_tmp_key(otp_new_secret)
|
119
|
+
json_response[otp_setup_param] = otp_user_key
|
120
|
+
json_response[otp_setup_raw_param] = otp_key
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def before_rodauth
|
125
|
+
if json_request?
|
126
|
+
if json_check_accept? && (accept = request.env['HTTP_ACCEPT']) && accept !~ json_accept_regexp
|
127
|
+
response.status = 406
|
128
|
+
json_response[json_response_error_key] = json_not_accepted_error_message
|
129
|
+
_return_json_response
|
130
|
+
end
|
131
|
+
|
132
|
+
unless request.post?
|
133
|
+
response.status = 405
|
134
|
+
response.headers['Allow'] = 'POST'
|
135
|
+
json_response[json_response_error_key] = json_non_post_error_message
|
136
|
+
return_json_response
|
137
|
+
end
|
138
|
+
elsif only_json?
|
139
|
+
response.status = json_response_error_status
|
140
|
+
response.write non_json_request_error_message
|
141
|
+
request.halt
|
142
|
+
end
|
143
|
+
|
144
|
+
super
|
145
|
+
end
|
146
|
+
|
147
|
+
def redirect(_)
|
148
|
+
return super unless use_json?
|
149
|
+
return_json_response
|
150
|
+
end
|
151
|
+
|
152
|
+
def return_json_response
|
153
|
+
_return_json_response
|
154
|
+
end
|
155
|
+
|
156
|
+
def _return_json_response
|
157
|
+
response.status ||= json_response_error_status if json_response[json_response_error_key]
|
158
|
+
response['Content-Type'] ||= json_response_content_type
|
159
|
+
response.write(_json_response_body(json_response))
|
160
|
+
request.halt
|
161
|
+
end
|
162
|
+
|
163
|
+
def include_success_messages?
|
164
|
+
!json_response_success_key.nil?
|
165
|
+
end
|
166
|
+
|
167
|
+
def _json_response_body(hash)
|
168
|
+
request.send(:convert_to_json, hash)
|
169
|
+
end
|
170
|
+
|
171
|
+
def json_response
|
172
|
+
@json_response ||= {}
|
173
|
+
end
|
174
|
+
|
175
|
+
def set_redirect_error_status(status)
|
176
|
+
if use_json? && json_response_custom_error_status?
|
177
|
+
response.status = status
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def set_response_error_status(status)
|
182
|
+
if use_json? && !json_response_custom_error_status?
|
183
|
+
status = json_response_error_status
|
184
|
+
end
|
185
|
+
|
186
|
+
super
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|