rodauth 2.46.0 → 2.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rodauth/features/active_sessions.rb +2 -2
- data/lib/rodauth/features/argon2.rb +6 -6
- data/lib/rodauth/features/audit_logging.rb +4 -3
- data/lib/rodauth/features/base.rb +84 -30
- data/lib/rodauth/features/change_password.rb +11 -2
- data/lib/rodauth/features/confirm_password.rb +1 -1
- data/lib/rodauth/features/create_account.rb +2 -1
- data/lib/rodauth/features/disallow_password_reuse.rb +2 -2
- data/lib/rodauth/features/email_auth.rb +13 -4
- data/lib/rodauth/features/email_base.rb +36 -1
- data/lib/rodauth/features/http_basic_auth.rb +6 -1
- data/lib/rodauth/features/internal_request.rb +4 -0
- data/lib/rodauth/features/json.rb +5 -1
- data/lib/rodauth/features/jwt.rb +9 -3
- data/lib/rodauth/features/jwt_cors.rb +2 -0
- data/lib/rodauth/features/jwt_refresh.rb +33 -18
- data/lib/rodauth/features/lockout.rb +24 -21
- data/lib/rodauth/features/login.rb +12 -4
- data/lib/rodauth/features/login_password_requirements_base.rb +43 -13
- data/lib/rodauth/features/otp.rb +21 -12
- data/lib/rodauth/features/otp_unlock.rb +2 -2
- data/lib/rodauth/features/password_complexity.rb +2 -2
- data/lib/rodauth/features/password_expiration.rb +0 -1
- data/lib/rodauth/features/password_grace_period.rb +8 -0
- data/lib/rodauth/features/password_pepper.rb +11 -1
- data/lib/rodauth/features/recovery_codes.rb +4 -1
- data/lib/rodauth/features/remember.rb +4 -2
- data/lib/rodauth/features/require_domain.rb +12 -0
- data/lib/rodauth/features/require_hmac_secret.rb +12 -0
- data/lib/rodauth/features/reset_password.rb +25 -20
- data/lib/rodauth/features/single_session.rb +6 -2
- data/lib/rodauth/features/sms_codes.rb +4 -1
- data/lib/rodauth/features/two_factor_base.rb +2 -1
- data/lib/rodauth/features/update_password_hash.rb +5 -1
- data/lib/rodauth/features/verify_account.rb +29 -20
- data/lib/rodauth/features/verify_login_change.rb +19 -11
- data/lib/rodauth/features/webauthn.rb +16 -11
- data/lib/rodauth/features/webauthn_login.rb +1 -10
- data/lib/rodauth/features/webauthn_verify_account.rb +0 -4
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +14 -12
- metadata +4 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Rodauth
|
|
4
4
|
Feature.define(:sms_codes, :SmsCodes) do
|
|
5
|
-
depends :two_factor_base
|
|
5
|
+
depends :two_factor_base, :require_domain
|
|
6
6
|
|
|
7
7
|
additional_form_tags 'sms_auth'
|
|
8
8
|
additional_form_tags 'sms_confirm'
|
|
@@ -176,6 +176,9 @@ module Rodauth
|
|
|
176
176
|
|
|
177
177
|
r.post do
|
|
178
178
|
transaction do
|
|
179
|
+
@sms = sms_ds.for_update.first
|
|
180
|
+
require_sms_available
|
|
181
|
+
|
|
179
182
|
if sms_code_match?(param(sms_code_param))
|
|
180
183
|
before_sms_auth
|
|
181
184
|
sms_remove_failures
|
|
@@ -158,7 +158,7 @@ module Rodauth
|
|
|
158
158
|
def require_two_factor_authenticated
|
|
159
159
|
unless two_factor_authenticated?
|
|
160
160
|
if two_factor_auth_return_to_requested_location?
|
|
161
|
-
|
|
161
|
+
set_session_return_to_path(two_factor_auth_redirect_session_key)
|
|
162
162
|
end
|
|
163
163
|
set_redirect_error_status(two_factor_need_authentication_error_status)
|
|
164
164
|
set_error_reason :two_factor_need_authentication
|
|
@@ -262,6 +262,7 @@ module Rodauth
|
|
|
262
262
|
end
|
|
263
263
|
|
|
264
264
|
def two_factor_update_session(auth_type)
|
|
265
|
+
raise Error, "attempt to have two factor authentication using same factor" if authenticated_by.include?(auth_type)
|
|
265
266
|
authenticated_by << auth_type
|
|
266
267
|
set_session_value(two_factor_setup_session_key, true)
|
|
267
268
|
end
|
|
@@ -9,7 +9,11 @@ module Rodauth
|
|
|
9
9
|
def password_match?(password)
|
|
10
10
|
if (result = super) && update_password_hash?
|
|
11
11
|
@update_password_hash = false
|
|
12
|
-
|
|
12
|
+
begin
|
|
13
|
+
set_password(password)
|
|
14
|
+
rescue Rodauth::SetPasswordFailure
|
|
15
|
+
return false
|
|
16
|
+
end
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
result
|
|
@@ -114,30 +114,30 @@ module Rodauth
|
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
r.post do
|
|
117
|
-
key =
|
|
118
|
-
|
|
119
|
-
set_redirect_error_status(invalid_key_error_status)
|
|
120
|
-
set_error_reason :invalid_verify_account_key
|
|
121
|
-
set_redirect_error_flash verify_account_error_flash
|
|
122
|
-
redirect verify_account_redirect
|
|
123
|
-
end
|
|
117
|
+
key = session_param(verify_account_session_key, verify_account_key_param)
|
|
118
|
+
select_key_for_update!
|
|
124
119
|
|
|
125
120
|
catch_error do
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
transaction do
|
|
122
|
+
handle_invalid_verify_account_key unless key && account_from_verify_account_key(key)
|
|
128
123
|
|
|
129
|
-
if
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
if verify_account_set_password?
|
|
125
|
+
password = param(password_param)
|
|
126
|
+
|
|
127
|
+
if require_password_confirmation? && password != param(password_confirm_param)
|
|
128
|
+
throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
|
|
129
|
+
end
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
unless password_meets_requirements?(password)
|
|
132
|
+
throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
|
|
133
|
+
end
|
|
135
134
|
end
|
|
136
|
-
end
|
|
137
135
|
|
|
138
|
-
transaction do
|
|
139
136
|
before_verify_account
|
|
140
|
-
verify_account
|
|
137
|
+
unless verify_account
|
|
138
|
+
remove_verify_account_key
|
|
139
|
+
handle_invalid_verify_account_key
|
|
140
|
+
end
|
|
141
141
|
if verify_account_set_password?
|
|
142
142
|
set_password(password)
|
|
143
143
|
end
|
|
@@ -171,7 +171,8 @@ module Rodauth
|
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
def verify_account
|
|
174
|
-
|
|
174
|
+
ds = account_ds.where(account_status_column=>account_unverified_status_value)
|
|
175
|
+
update_account({account_status_column=>account_open_status_value}, ds) == 1
|
|
175
176
|
end
|
|
176
177
|
|
|
177
178
|
def verify_account_email_resend
|
|
@@ -196,7 +197,7 @@ module Rodauth
|
|
|
196
197
|
end
|
|
197
198
|
|
|
198
199
|
def account_from_verify_account_key(key)
|
|
199
|
-
|
|
200
|
+
set_account(_account_from_verify_account_key(key), :verify_account)
|
|
200
201
|
end
|
|
201
202
|
|
|
202
203
|
def account_initial_status_value
|
|
@@ -208,7 +209,7 @@ module Rodauth
|
|
|
208
209
|
end
|
|
209
210
|
|
|
210
211
|
def get_verify_account_key(id)
|
|
211
|
-
verify_account_ds(id).get(verify_account_key_column)
|
|
212
|
+
apply_key_for_update(verify_account_ds(id)).get(verify_account_key_column)
|
|
212
213
|
end
|
|
213
214
|
|
|
214
215
|
def skip_status_checks?
|
|
@@ -318,5 +319,13 @@ module Rodauth
|
|
|
318
319
|
def _account_from_verify_account_key(token)
|
|
319
320
|
account_from_key(token, account_unverified_status_value){|id| get_verify_account_key(id)}
|
|
320
321
|
end
|
|
322
|
+
|
|
323
|
+
def handle_invalid_verify_account_key
|
|
324
|
+
remove_session_value(verify_account_session_key)
|
|
325
|
+
set_redirect_error_status(invalid_key_error_status)
|
|
326
|
+
set_error_reason :invalid_verify_account_key
|
|
327
|
+
set_redirect_error_flash verify_account_error_flash
|
|
328
|
+
redirect verify_account_redirect
|
|
329
|
+
end
|
|
321
330
|
end
|
|
322
331
|
end
|
|
@@ -74,15 +74,18 @@ module Rodauth
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
r.post do
|
|
77
|
-
key =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
set_error_reason :invalid_verify_login_change_key
|
|
81
|
-
set_redirect_error_flash verify_login_change_error_flash
|
|
82
|
-
redirect verify_login_change_redirect
|
|
83
|
-
end
|
|
77
|
+
key = session_param(verify_login_change_session_key, verify_login_change_key_param)
|
|
78
|
+
remove_session_value(verify_login_change_session_key)
|
|
79
|
+
select_key_for_update!
|
|
84
80
|
|
|
85
81
|
transaction do
|
|
82
|
+
unless key && account_from_verify_login_change_key(key)
|
|
83
|
+
set_redirect_error_status(invalid_key_error_status)
|
|
84
|
+
set_error_reason :invalid_verify_login_change_key
|
|
85
|
+
set_redirect_error_flash verify_login_change_error_flash
|
|
86
|
+
redirect verify_login_change_redirect
|
|
87
|
+
end
|
|
88
|
+
|
|
86
89
|
before_verify_login_change
|
|
87
90
|
unless verify_login_change
|
|
88
91
|
set_redirect_error_status(invalid_key_error_status)
|
|
@@ -98,7 +101,6 @@ module Rodauth
|
|
|
98
101
|
autologin_session('verify_login_change')
|
|
99
102
|
end
|
|
100
103
|
|
|
101
|
-
remove_session_value(verify_login_change_session_key)
|
|
102
104
|
verify_login_change_response
|
|
103
105
|
end
|
|
104
106
|
end
|
|
@@ -120,7 +122,7 @@ module Rodauth
|
|
|
120
122
|
end
|
|
121
123
|
|
|
122
124
|
def account_from_verify_login_change_key(key)
|
|
123
|
-
|
|
125
|
+
set_account(_account_from_verify_login_change_key(key), :verify_login_change)
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
def send_verify_login_change_email(login)
|
|
@@ -132,7 +134,9 @@ module Rodauth
|
|
|
132
134
|
end
|
|
133
135
|
|
|
134
136
|
def get_verify_login_change_login_and_key(id)
|
|
135
|
-
verify_login_change_ds(id)
|
|
137
|
+
ds = verify_login_change_ds(id)
|
|
138
|
+
ds.where(Sequel::CURRENT_TIMESTAMP > verify_login_change_deadline_column).delete
|
|
139
|
+
apply_key_for_update(ds).get([verify_login_change_login_column, verify_login_change_key_column])
|
|
136
140
|
end
|
|
137
141
|
|
|
138
142
|
def change_login_notice_flash
|
|
@@ -210,10 +214,14 @@ module Rodauth
|
|
|
210
214
|
end
|
|
211
215
|
|
|
212
216
|
def _account_from_verify_login_change_key(token)
|
|
213
|
-
account_from_key(token) do |id|
|
|
217
|
+
account_from_key(token, verify_login_change_account_status_value) do |id|
|
|
214
218
|
@verify_login_change_new_login, key = get_verify_login_change_login_and_key(id)
|
|
215
219
|
key
|
|
216
220
|
end
|
|
217
221
|
end
|
|
222
|
+
|
|
223
|
+
def verify_login_change_account_status_value
|
|
224
|
+
account_open_status_value
|
|
225
|
+
end
|
|
218
226
|
end
|
|
219
227
|
end
|
|
@@ -4,7 +4,7 @@ require 'webauthn'
|
|
|
4
4
|
|
|
5
5
|
module Rodauth
|
|
6
6
|
Feature.define(:webauthn, :Webauthn) do
|
|
7
|
-
depends :two_factor_base
|
|
7
|
+
depends :two_factor_base, :require_domain, :require_hmac_secret
|
|
8
8
|
|
|
9
9
|
loaded_templates %w'webauthn-setup webauthn-auth webauthn-remove'
|
|
10
10
|
|
|
@@ -164,7 +164,7 @@ module Rodauth
|
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
route(:webauthn_setup) do |r|
|
|
167
|
-
require_authentication unless
|
|
167
|
+
require_authentication unless webauthn_modification_authenticated?
|
|
168
168
|
require_account_session
|
|
169
169
|
before_webauthn_setup_route
|
|
170
170
|
|
|
@@ -185,7 +185,7 @@ module Rodauth
|
|
|
185
185
|
raise Sequel::Rollback
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
if !two_factor_authenticated? && !authenticated_by.include?('webauthn')
|
|
189
189
|
webauthn_update_session(webauthn_credential.id)
|
|
190
190
|
two_factor_update_session('webauthn')
|
|
191
191
|
end
|
|
@@ -205,7 +205,7 @@ module Rodauth
|
|
|
205
205
|
end
|
|
206
206
|
|
|
207
207
|
route(:webauthn_remove) do |r|
|
|
208
|
-
require_authentication unless
|
|
208
|
+
require_authentication unless webauthn_modification_authenticated?
|
|
209
209
|
require_account_session
|
|
210
210
|
require_webauthn_setup
|
|
211
211
|
before_webauthn_remove_route
|
|
@@ -290,9 +290,9 @@ module Rodauth
|
|
|
290
290
|
# it is safe for the second one to use the webauthn user id inserted by the other request.
|
|
291
291
|
# If there is still no webauthn user id at this point, then we'll just reraise the
|
|
292
292
|
# exception.
|
|
293
|
-
# :
|
|
293
|
+
# simplecov:disable
|
|
294
294
|
raise e unless webauthn_id = webauthn_user_ids_ds.get(webauthn_user_ids_webauthn_id_column)
|
|
295
|
-
# :
|
|
295
|
+
# simplecov:enable
|
|
296
296
|
end
|
|
297
297
|
end
|
|
298
298
|
|
|
@@ -419,7 +419,7 @@ module Rodauth
|
|
|
419
419
|
name: webauthn_rp_name,
|
|
420
420
|
)
|
|
421
421
|
end
|
|
422
|
-
# :
|
|
422
|
+
# simplecov:disable
|
|
423
423
|
else
|
|
424
424
|
def webauthn_relying_party
|
|
425
425
|
WebAuthn::RelyingParty.new(
|
|
@@ -428,7 +428,7 @@ module Rodauth
|
|
|
428
428
|
name: webauthn_rp_name,
|
|
429
429
|
)
|
|
430
430
|
end
|
|
431
|
-
# :
|
|
431
|
+
# simplecov:enable
|
|
432
432
|
end
|
|
433
433
|
|
|
434
434
|
def webauthn_create_relying_party_opts
|
|
@@ -443,7 +443,7 @@ module Rodauth
|
|
|
443
443
|
def _override_webauthn_credential_response_verify(webauthn_credential)
|
|
444
444
|
# no need to override
|
|
445
445
|
end
|
|
446
|
-
# :
|
|
446
|
+
# simplecov:disable
|
|
447
447
|
else
|
|
448
448
|
def webauthn_create_relying_party_opts
|
|
449
449
|
{:rp => {:name=>webauthn_rp_name, :id=>webauthn_rp_id}}
|
|
@@ -466,7 +466,12 @@ module Rodauth
|
|
|
466
466
|
super(expected_challenge, expected_origin || origin, **kw)
|
|
467
467
|
end
|
|
468
468
|
end
|
|
469
|
-
# :
|
|
469
|
+
# simplecov:enable
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def webauthn_modification_authenticated?
|
|
473
|
+
two_factor_login_type_match?('webauthn') &&
|
|
474
|
+
(!uses_two_factor_authentication? || two_factor_modifications_require_password?)
|
|
470
475
|
end
|
|
471
476
|
|
|
472
477
|
def _two_factor_auth_links
|
|
@@ -501,7 +506,7 @@ module Rodauth
|
|
|
501
506
|
end
|
|
502
507
|
|
|
503
508
|
def webauthn_account_id
|
|
504
|
-
|
|
509
|
+
account_id_or_session_value
|
|
505
510
|
end
|
|
506
511
|
|
|
507
512
|
def webauthn_user_ids_ds
|
|
@@ -76,7 +76,7 @@ module Rodauth
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def account_from_webauthn_login
|
|
79
|
-
account_from_login(login_param_value)
|
|
79
|
+
@account_retrieval_type == :login ? @account : account_from_login(login_param_value)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def webauthn_login_options?
|
|
@@ -91,14 +91,5 @@ module Rodauth
|
|
|
91
91
|
end
|
|
92
92
|
forms
|
|
93
93
|
end
|
|
94
|
-
|
|
95
|
-
def webauthn_account_id
|
|
96
|
-
case @current_route
|
|
97
|
-
when :login, :webauthn_login, nil # nil for internal request
|
|
98
|
-
account_id
|
|
99
|
-
else
|
|
100
|
-
super
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
94
|
end
|
|
104
95
|
end
|
data/lib/rodauth/version.rb
CHANGED
data/lib/rodauth.rb
CHANGED
|
@@ -7,6 +7,8 @@ module Rodauth
|
|
|
7
7
|
SCOPE_INSTANCE_VARIABLES = [:@_rodauths, :@_rodauth].freeze
|
|
8
8
|
|
|
9
9
|
class ConfigurationError < StandardError; end
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
class SetPasswordFailure < Error; end
|
|
10
12
|
|
|
11
13
|
def self.lib(opts=OPTS, &block)
|
|
12
14
|
require 'roda'
|
|
@@ -36,9 +38,9 @@ module Rodauth
|
|
|
36
38
|
when false
|
|
37
39
|
# nothing
|
|
38
40
|
when :rack_csrf
|
|
39
|
-
# :
|
|
41
|
+
# simplecov:disable
|
|
40
42
|
app.plugin :csrf
|
|
41
|
-
# :
|
|
43
|
+
# simplecov:enable
|
|
42
44
|
else
|
|
43
45
|
app.plugin :route_csrf
|
|
44
46
|
end
|
|
@@ -184,9 +186,9 @@ module Rodauth
|
|
|
184
186
|
feature.module_eval(&block)
|
|
185
187
|
configuration.def_configuration_methods(feature)
|
|
186
188
|
|
|
187
|
-
# :
|
|
189
|
+
# simplecov:disable
|
|
188
190
|
if constant
|
|
189
|
-
# :
|
|
191
|
+
# simplecov:enable
|
|
190
192
|
Rodauth.const_set(constant, feature)
|
|
191
193
|
Rodauth::FeatureConfiguration.const_set(constant, configuration)
|
|
192
194
|
end
|
|
@@ -209,9 +211,9 @@ module Rodauth
|
|
|
209
211
|
if RUBY_VERSION >= '2.5'
|
|
210
212
|
DEPRECATED_ARGS = [{:uplevel=>1}]
|
|
211
213
|
else
|
|
212
|
-
# :
|
|
214
|
+
# simplecov:disable
|
|
213
215
|
DEPRECATED_ARGS = []
|
|
214
|
-
# :
|
|
216
|
+
# simplecov:enable
|
|
215
217
|
end
|
|
216
218
|
def def_deprecated_alias(new, old)
|
|
217
219
|
configuration_module_eval do
|
|
@@ -346,7 +348,7 @@ module Rodauth
|
|
|
346
348
|
auth_private_methods(meth)
|
|
347
349
|
end
|
|
348
350
|
|
|
349
|
-
# :
|
|
351
|
+
# simplecov:disable
|
|
350
352
|
def auth_cached_method(meth, iv=:"@#{meth}") # :nodoc:
|
|
351
353
|
# Non-shape friendly historical method.
|
|
352
354
|
# RODAUTH3: Remove
|
|
@@ -361,7 +363,7 @@ module Rodauth
|
|
|
361
363
|
alias_method(meth, meth)
|
|
362
364
|
auth_private_methods(meth)
|
|
363
365
|
end
|
|
364
|
-
# :
|
|
366
|
+
# simplecov:enable
|
|
365
367
|
|
|
366
368
|
[:notice_flash, :error_flash, :button].each do |meth|
|
|
367
369
|
define_method(meth) do |v, name=feature_name|
|
|
@@ -375,11 +377,11 @@ module Rodauth
|
|
|
375
377
|
|
|
376
378
|
def initialize(auth, &block)
|
|
377
379
|
@auth = auth
|
|
378
|
-
# :
|
|
380
|
+
# simplecov:disable
|
|
379
381
|
# Only for backwards compatibility
|
|
380
382
|
# RODAUTH3: Remove
|
|
381
383
|
apply(&block) if block
|
|
382
|
-
# :
|
|
384
|
+
# simplecov:enable
|
|
383
385
|
end
|
|
384
386
|
|
|
385
387
|
def apply(&block)
|
|
@@ -469,12 +471,12 @@ module Rodauth
|
|
|
469
471
|
|
|
470
472
|
ivs
|
|
471
473
|
end
|
|
472
|
-
# :
|
|
474
|
+
# simplecov:disable
|
|
473
475
|
else
|
|
474
476
|
def make_shape_friendly # :nodoc:
|
|
475
477
|
end
|
|
476
478
|
end
|
|
477
|
-
# :
|
|
479
|
+
# simplecov:enable
|
|
478
480
|
end
|
|
479
481
|
|
|
480
482
|
def self.inherited(subclass)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rodauth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.48.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Evans
|
|
@@ -293,6 +293,8 @@ files:
|
|
|
293
293
|
- lib/rodauth/features/path_class_methods.rb
|
|
294
294
|
- lib/rodauth/features/recovery_codes.rb
|
|
295
295
|
- lib/rodauth/features/remember.rb
|
|
296
|
+
- lib/rodauth/features/require_domain.rb
|
|
297
|
+
- lib/rodauth/features/require_hmac_secret.rb
|
|
296
298
|
- lib/rodauth/features/reset_password.rb
|
|
297
299
|
- lib/rodauth/features/reset_password_notify.rb
|
|
298
300
|
- lib/rodauth/features/reset_password_verifies_account.rb
|
|
@@ -405,7 +407,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
405
407
|
- !ruby/object:Gem::Version
|
|
406
408
|
version: '0'
|
|
407
409
|
requirements: []
|
|
408
|
-
rubygems_version: 4.0.
|
|
410
|
+
rubygems_version: 4.0.20
|
|
409
411
|
specification_version: 4
|
|
410
412
|
summary: Authentication and Account Management Framework for Rack Applications
|
|
411
413
|
test_files: []
|