rodauth 2.30.0 → 2.31.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8270d10e6c0fbe554fc322958893f2c8069363af6455c0d17b0a7d3aafab11bd
4
- data.tar.gz: 6eae8a9487764a9b189b27b8f1588e1516d86a6953fdabe0cff6adfd84facb5a
3
+ metadata.gz: 34f1a4bc530025482c6550375de67bf9dcbe4ea75ca9f4f775d179805629e115
4
+ data.tar.gz: 75339b39caf60463c076459e1fc21fd4d2291e4bc756c4cc5107edf7462c0186
5
5
  SHA512:
6
- metadata.gz: d4dca9a0819842a478fac05f138b07452a054c7e498821eee7e61ab49640eccc46974d38c3aea1f71bf226eb7f76b03888a6734d7ae2139bcc74aca9a24bad6c
7
- data.tar.gz: 5075118b0c6a8b27df251a2e6bfcd45e3dacc7ba1402bf5bacfc38a50b93b424a0ccdb479b1f5541d589f5687a8424c7319a194c0b01a80c762b725065190013
6
+ metadata.gz: 9e66e012fca7c52bd25206fd7711921148e8372a249047687e5429074dd9ea64461dcbbda1a0412b16920270814fef9fa5954dda701ae30af66e7a78a7975855
7
+ data.tar.gz: f7c9ec1aac7150a3fa1b4d2af3e8278f16ef3982d5cd84b6b2984d41b5f5b70f60e9f7c0e099d338055201d820bd099093609b2683086c5a8ec9fdf53dee2e58
data/CHANGELOG CHANGED
@@ -1,3 +1,17 @@
1
+ === 2.31.0 (2023-08-22)
2
+
3
+ * Make clear_session work correctly for internal requests (janko) (#359)
4
+
5
+ * Support webauthn_invalid_webauthn_id_message configuration method in the webauthn_autofill feature (janko) (#356)
6
+
7
+ * Support webauth features in the internal_request feature (janko) (#355)
8
+
9
+ * Allow WebAuthn login to count for two factors if user verification is provided (janko) (#354)
10
+
11
+ * Allow explicit use of p_cost in argon2 feature if using argon2 2.1+ (estebanz01) (#353)
12
+
13
+ * Add json_response_error? configuration method to json feature, for whether response indicates an error (opya) (#340)
14
+
1
15
  === 2.30.0 (2023-05-22)
2
16
 
3
17
  * Make load_memory in the remember feature not raise NoMethodError if logged in when the account no longer exists (jeremyevans) (#331)
data/README.rdoc CHANGED
@@ -98,6 +98,9 @@ rqrcode :: Used by the otp feature
98
98
  jwt :: Used by the jwt feature
99
99
  webauthn :: Used by the webauthn feature
100
100
 
101
+ You can use <tt>gem install --development rodauth</tt> to install
102
+ the development dependencies in order to run tests.
103
+
101
104
  == Security
102
105
 
103
106
  === Password Hash Access Via Database Functions
@@ -38,6 +38,7 @@ Rodauth will call +set_error_reason+ with:
38
38
  * :invalid_verify_account_key
39
39
  * :invalid_verify_login_change_key
40
40
  * :invalid_webauthn_auth_param
41
+ * :invalid_webauthn_id
41
42
  * :invalid_webauthn_remove_param
42
43
  * :invalid_webauthn_setup_param
43
44
  * :invalid_webauthn_sign_count
@@ -10,7 +10,7 @@ or their company's name.
10
10
  Let's assume you wanted to wanted to store the additional field(s) directly on
11
11
  the +accounts+ table:
12
12
 
13
- atler_table :accounts do
13
+ alter_table :accounts do
14
14
  add_column :name, String
15
15
  end
16
16
 
@@ -0,0 +1,17 @@
1
+ = Render confirmation view
2
+
3
+ Most Rodauth actions redirect and display a flash notice after they're succesfully performed. However, in some cases you may wish to render a view confirming that the action was succesful, for nicer user experience.
4
+
5
+ For example, when the user creates an account, you might render a page with a call to action to verify their account. Assuming you've created an +account_created+ view template alongside your other Rodauth templates, you can configure the following:
6
+
7
+ after_create_account do
8
+ # render "account_created" view template with page title of "Account created!"
9
+ return_response view("account_created", "Account created!")
10
+ end
11
+
12
+ Similarly, when the user has requested a password reset, you can render a page telling them to check their email:
13
+
14
+ after_reset_password_request do
15
+ # render "password_reset_sent" view template with page title of "Password sent!"
16
+ return_response view("password_reset_sent", "Password sent!")
17
+ end
@@ -461,3 +461,79 @@ account login, before the change.
461
461
 
462
462
  Options:
463
463
  +:verify_login_change_key+ :: The verify login change key for the account. This allows verifying login changes by key, without knowing the account id or login.
464
+
465
+ === WebAuthn
466
+
467
+ ==== webauthn_setup_params (requires account)
468
+
469
+ The +webauthn_setup_params+ method returns a hash with +:webauthn_setup+,
470
+ +:webauthn_setup_challenge+ and +:webauthn_setup_challenge_hmac+ keys.
471
+
472
+ The +:webauthn_setup+ options should be provided to the client for WebAuthn
473
+ registration, while +:webauthn_setup_challenge+ and
474
+ +webauthn_setup_challenge_hmac+ should be passed to the +webauthn_setup+
475
+ method.
476
+
477
+ ==== webauthn_setup (requires account)
478
+
479
+ The +webauthn_setup+ method creates a WebAuthn credential for the account.
480
+
481
+ Options:
482
+ +:webauthn_setup+ :: The WebAuthn credential provided by the client during registration.
483
+ +:webauthn_setup_challenge+ :: The WebAuthn challenge generated for registration.
484
+ +:webauthn_setup_challenge_hmac+ :: The HMAC of the WebAuthn challenge generated for registration.
485
+
486
+ ==== webauthn_auth_params (requires account)
487
+
488
+ The +webauthn_auth_params+ method returns a hash with +:webauthn_auth+,
489
+ +:webauthn_auth_challenge+ and +:webauthn_auth_challenge_hmac+ keys.
490
+
491
+ The +:webauthn_auth+ options should be provided to the client for WebAuthn
492
+ authentication, while +:webauthn_auth_challenge+ and
493
+ +webauthn_auth_challenge_hmac+ should be passed to the +webauthn_auth+ method.
494
+
495
+ ==== webauthn_auth (requires account)
496
+
497
+ The +webauthn_auth+ method determines if the given WebAuthn credential is valid
498
+ for the account.
499
+
500
+ Options:
501
+ +:webauthn_auth+ :: The WebAuthn credential provided by the client during authentication.
502
+ +:webauthn_auth_challenge+ :: The WebAuthn challenge generated for authentication.
503
+ +:webauthn_auth_challenge_hmac+ :: The HMAC of the WebAuthn challenge generated for authentication.
504
+
505
+ ==== webauthn_remove (requires account)
506
+
507
+ The +webauthn_remove+ methods deletes the given WebAuthn credential for the
508
+ account.
509
+
510
+ Options:
511
+ +:webauthn_remove+ :: The ID of the WebAuthn credential to delete.
512
+
513
+ === WebAuthn Login
514
+
515
+ ==== webauthn_login_params (requires account or login)
516
+
517
+ The +webauthn_login_params+ method returns a hash with +:webauthn_auth+,
518
+ +:webauthn_auth_challenge+ and +:webauthn_auth_challenge_hmac+ keys.
519
+
520
+ The +:webauthn_auth+ options should be provided to the client for WebAuthn
521
+ authentication, while +:webauthn_auth_challenge+ and
522
+ +webauthn_auth_challenge_hmac+ should be passed to the +webauthn_login+ method.
523
+
524
+ ==== webauthn_login (requires account or login)
525
+
526
+ The +webauthn_login+ method determines if the given WebAuthn credential is
527
+ valid for the given account.
528
+
529
+ This method will return the account id if the WebAuthn credential is valid.
530
+
531
+ Options:
532
+ +:webauthn_auth+ :: The WebAuthn credential provided by the client during authentication.
533
+ +:webauthn_auth_challenge+ :: The WebAuthn challenge generated for authentication.
534
+ +:webauthn_auth_challenge_hmac+ :: The HMAC of the WebAuthn challenge generated for authentication.
535
+
536
+ === WebAuthn Autofill
537
+
538
+ Enabling this feature modifies +webauthn_login_params+ and +webauthn_login+
539
+ methods not to require an account or login.
data/doc/json.rdoc CHANGED
@@ -41,6 +41,7 @@ json_not_accepted_error_message :: The error message to display if +json_check_a
41
41
  json_request_content_type_regexp :: The regexp to use to recognize a request as a json request.
42
42
  json_response_content_type :: The content type to set for json responses, <tt>application/json</tt> by default.
43
43
  json_response_custom_error_status? :: Whether to use custom error statuses, instead of always using +json_response_error_status+, true by default, can be set to false for backwards compatibility with Rodauth 1.
44
+ json_response_error? :: Whether the current JSON response indicates an error. By default, returns whether +json_response_error_key+ is set.
44
45
  json_response_error_key :: The JSON result key containing an error message, +error+ by default.
45
46
  json_response_error_status :: The HTTP status code to use for JSON error responses if not using custom error statuses, 400 by default.
46
47
  json_response_field_error_key :: The JSON result key containing an field error message, <tt>field-error</tt> by default.
data/doc/jwt.rdoc CHANGED
@@ -5,7 +5,7 @@ that ship with Rodauth, using JWT (JSON Web Tokens) to hold the
5
5
  session information. It depends on the json feature.
6
6
 
7
7
  In order to use this feature, you have to set the +jwt_secret+ configuration
8
- option the secret used to cryptographically protect the token.
8
+ option with the secret used to cryptographically protect the token.
9
9
 
10
10
  To use this JSON API, when processing responses for requests to a Rodauth
11
11
  endpoint, check for the Authorization header, and use the value of the
@@ -26,6 +26,11 @@ request in your Roda app, you can call the +rodauth.valid_jwt?+ method. If
26
26
  +rodauth.valid_jwt?+ returns true, the contents of the jwt can be retrieved
27
27
  from +rodauth.session+.
28
28
 
29
+ Logging the session out does not invalidate the previous JWT token by default.
30
+ If you would like this behavior, you can use the active_sessions feature, which
31
+ stores session identifiers in the database and deletes them when the session
32
+ expires. This provides a whitelist approach of revoking JWT tokens.
33
+
29
34
  == Auth Value Methods
30
35
 
31
36
  invalid_jwt_format_error_message :: The error message to use when a JWT with an invalid format is submitted in the Authorization header.
data/doc/jwt_refresh.rdoc CHANGED
@@ -7,7 +7,7 @@ When this feature is used, the access and refresh token are provided
7
7
  at login in the response body (the access token is still provided in the Authorization
8
8
  header), and for any subsequent POST to <tt>/jwt-refresh</tt>.
9
9
 
10
- Note that using the refresh token invalides the token and creates
10
+ Note that using the refresh token invalidates the token and creates
11
11
  a new access token with an updated lifetime. However, it does not invalidate
12
12
  older access tokens. Older access tokens remain valid until they expire. You
13
13
  can use the active_sessions feature if you want previous access tokens to be invalid
@@ -0,0 +1,47 @@
1
+ = New Features
2
+
3
+ * The internal_request feature now supports WebAuthn, using
4
+ the following methods:
5
+
6
+ * With the webauthn feature:
7
+ * webauthn_setup_params
8
+ * webauthn_setup
9
+ * webauthn_auth_params
10
+ * webauthn_auth
11
+ * webauthn_remove
12
+
13
+ * With the webauthn_login feature:
14
+ * webauthn_login_params
15
+ * webauthn_login
16
+
17
+ * A webauthn_login_user_verification_additional_factor? configuration
18
+ method has been added to the webauthn_login feature. By default,
19
+ this method returns false. If you configure the method to return
20
+ true, and the WebAuthn credential provided specifies that it
21
+ verified the user, then this will treat the user verification as
22
+ a second factor, so the user will be considered multifactor
23
+ authenticated after successful login. You should only set this
24
+ method to true if you consider the WebAuthn user verification
25
+ strong enough to be a independent factor.
26
+
27
+ * A json_response_error? configuration method has been added to the
28
+ json feature. This should return whether the current response
29
+ should be treated as an error by the json feature. By default,
30
+ it is true if json_response_error_key is set in the response,
31
+ since that is the default place that Rodauth stores errors when
32
+ using the json feature.
33
+
34
+ * A webauthn_invalid_webauthn_id_message configuration method has
35
+ been added for customizing the error message used for invalid
36
+ WebAuthn IDs.
37
+
38
+ = Other Improvements
39
+
40
+ * The argon2 feature now supports setting the Argon2 p_cost if
41
+ argon2 2.1+ is installed.
42
+
43
+ * An :invalid_webauthn_id error reason is now used for invalid
44
+ WebAuthn IDs.
45
+
46
+ * The clear_session method now works as expected for internal
47
+ requests.
@@ -4,10 +4,15 @@ The webauthn_autofill feature enables autofill UI (aka "conditional mediation")
4
4
  for WebAuthn credentials, logging the user in on selection. It depends on the
5
5
  webauthn_login feature.
6
6
 
7
+ This feature allows generating WebAuthn credential options and submitting a
8
+ WebAuthn login request without providing a login, which can be used
9
+ independently from the autofill UI.
10
+
7
11
  == Auth Value Methods
8
12
 
9
13
  webauthn_autofill_js :: The javascript code to execute on the login page to enable autofill UI.
10
14
  webauthn_autofill_js_route :: The route to the webauthn autofill javascript file.
15
+ webauthn_invalid_webauthn_id_message :: The error message to show when provided WebAuthn ID wasn't found in the database.
11
16
 
12
17
  == Auth Methods
13
18
 
@@ -5,6 +5,7 @@ WebAuthn. It depends on the login and webauthn features.
5
5
 
6
6
  == Auth Value Methods
7
7
 
8
+ webauthn_login_user_verification_additional_factor? :: Whether passwordless login via WebAuthn should consider user verification as 2nd factor when using multifactor authentication, false by default. Setting this to true means that the app trusts the user verification done by the authenticator is strong enough to be considered an additional factor.
8
9
  webauthn_login_error_flash :: The flash error to show if there is a failure during passwordless login via WebAuthn.
9
10
  webauthn_login_failure_redirect :: Whether to redirect if there is a failure during passwordless login via WebAuthn.
10
11
  webauthn_login_route :: The route to the webauthn login action.
@@ -43,7 +43,7 @@ module Rodauth
43
43
 
44
44
  def password_hash_cost
45
45
  return super unless use_argon2?
46
- argon2_hash_cost
46
+ argon2_hash_cost
47
47
  end
48
48
 
49
49
  def password_hash_match?(hash, password)
@@ -60,21 +60,40 @@ module Rodauth
60
60
  ::Argon2::Password.new(argon2_params).create(password)
61
61
  end
62
62
 
63
- def extract_password_hash_cost(hash)
64
- return super unless argon2_hash_algorithm?(hash )
63
+ if Argon2::VERSION >= '2.1'
64
+ def extract_password_hash_cost(hash)
65
+ return super unless argon2_hash_algorithm?(hash )
65
66
 
66
- /\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+)/ =~ hash
67
- { t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i }
68
- end
67
+ /\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+),p=(\d+)/ =~ hash
68
+ { t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i, p_cost: $3.to_i }
69
+ end
69
70
 
70
- if ENV['RACK_ENV'] == 'test'
71
- def argon2_hash_cost
72
- {t_cost: 1, m_cost: 3}
71
+ if ENV['RACK_ENV'] == 'test'
72
+ def argon2_hash_cost
73
+ { t_cost: 1, m_cost: 3, p_cost: 1 }
74
+ end
75
+ # :nocov:
76
+ else
77
+ def argon2_hash_cost
78
+ { t_cost: 2, m_cost: 16, p_cost: 1 }
79
+ end
73
80
  end
74
- # :nocov:
75
81
  else
76
- def argon2_hash_cost
77
- {t_cost: 2, m_cost: 16}
82
+ def extract_password_hash_cost(hash)
83
+ return super unless argon2_hash_algorithm?(hash )
84
+
85
+ /\A\$argon2id\$v=\d+\$m=(\d+),t=(\d+)/ =~ hash
86
+ { t_cost: $2.to_i, m_cost: Math.log2($1.to_i).to_i }
87
+ end
88
+
89
+ if ENV['RACK_ENV'] == 'test'
90
+ def argon2_hash_cost
91
+ { t_cost: 1, m_cost: 3 }
92
+ end
93
+ else
94
+ def argon2_hash_cost
95
+ { t_cost: 2, m_cost: 16 }
96
+ end
78
97
  end
79
98
  end
80
99
  # :nocov:
@@ -50,6 +50,10 @@ module Rodauth
50
50
  @params[k]
51
51
  end
52
52
 
53
+ def clear_session
54
+ @session.clear
55
+ end
56
+
53
57
  def set_error_flash(message)
54
58
  @flash = message
55
59
  _handle_internal_request_error
@@ -81,6 +85,24 @@ module Rodauth
81
85
  _return_from_internal_request(recovery_codes)
82
86
  end
83
87
 
88
+ def webauthn_setup_view
89
+ cred = new_webauthn_credential
90
+ _return_from_internal_request({
91
+ webauthn_setup: cred.as_json,
92
+ webauthn_setup_challenge: cred.challenge,
93
+ webauthn_setup_challenge_hmac: compute_hmac(cred.challenge)
94
+ })
95
+ end
96
+
97
+ def webauthn_auth_view
98
+ cred = webauthn_credential_options_for_get
99
+ _return_from_internal_request({
100
+ webauthn_auth: cred.as_json,
101
+ webauthn_auth_challenge: cred.challenge,
102
+ webauthn_auth_challenge_hmac: compute_hmac(cred.challenge)
103
+ })
104
+ end
105
+
84
106
  def handle_internal_request(meth)
85
107
  catch(:halt) do
86
108
  _around_rodauth do
@@ -153,6 +175,11 @@ module Rodauth
153
175
  _set_login_param_from_account
154
176
  end
155
177
 
178
+ def before_webauthn_login_route
179
+ super
180
+ _set_login_param_from_account
181
+ end
182
+
156
183
  def account_from_key(token, status_id=nil)
157
184
  return super unless session_value
158
185
  return unless yield session_value
@@ -232,6 +259,25 @@ module Rodauth
232
259
  _handle_otp_setup(request)
233
260
  end
234
261
 
262
+ def _handle_webauthn_setup_params(request)
263
+ request.env['REQUEST_METHOD'] = 'GET'
264
+ _handle_webauthn_setup(request)
265
+ end
266
+
267
+ def _handle_webauthn_auth_params(request)
268
+ request.env['REQUEST_METHOD'] = 'GET'
269
+ _handle_webauthn_auth(request)
270
+ end
271
+
272
+ def _handle_webauthn_login_params(request)
273
+ _set_login_param_from_account
274
+ unless webauthn_login_options?
275
+ raise InternalRequestError, "no login provided" unless param_or_nil(login_param)
276
+ raise InternalRequestError, "no account for login"
277
+ end
278
+ webauthn_auth_view
279
+ end
280
+
235
281
  def _predicate_internal_request(meth, request)
236
282
  _return_false_on_error!
237
283
  _set_internal_request_return_value(true)
@@ -302,7 +348,7 @@ module Rodauth
302
348
  session[rodauth.session_key] = account_id
303
349
  unless authenticated_by = opts.delete(:authenticated_by)
304
350
  authenticated_by = case route
305
- when :otp_auth, :sms_request, :sms_auth, :recovery_auth, :valid_otp_auth?, :valid_sms_auth?, :valid_recovery_auth?
351
+ when :otp_auth, :sms_request, :sms_auth, :recovery_auth, :webauthn_auth, :webauthn_auth_params, :valid_otp_auth?, :valid_sms_auth?, :valid_recovery_auth?
306
352
  ['internal1']
307
353
  else
308
354
  ['internal1', 'internal2']
@@ -22,6 +22,7 @@ module Rodauth
22
22
 
23
23
  auth_methods(
24
24
  :json_request?,
25
+ :json_response_error?
25
26
  )
26
27
 
27
28
  auth_private_methods :json_response_body
@@ -65,6 +66,10 @@ module Rodauth
65
66
  return_json_response
66
67
  end
67
68
 
69
+ def json_response_error?
70
+ !!json_response[json_response_error_key]
71
+ end
72
+
68
73
  private
69
74
 
70
75
  def before_two_factor_manage_route
@@ -172,7 +177,7 @@ module Rodauth
172
177
  end
173
178
 
174
179
  def _return_json_response
175
- response.status ||= json_response_error_status if json_response[json_response_error_key]
180
+ response.status ||= json_response_error_status if json_response_error?
176
181
  response['Content-Type'] ||= json_response_content_type
177
182
  return_response _json_response_body(json_response)
178
183
  end
@@ -112,6 +112,12 @@ module Rodauth
112
112
 
113
113
  def_deprecated_alias :webauthn_credential_options_for_get, :webauth_credential_options_for_get
114
114
 
115
+ internal_request_method :webauthn_setup_params
116
+ internal_request_method :webauthn_setup
117
+ internal_request_method :webauthn_auth_params
118
+ internal_request_method :webauthn_auth
119
+ internal_request_method :webauthn_remove
120
+
115
121
  route(:webauthn_auth_js) do |r|
116
122
  before_webauthn_auth_js_route
117
123
  r.get do
@@ -6,6 +6,8 @@ module Rodauth
6
6
 
7
7
  auth_value_method :webauthn_autofill_js, File.binread(File.expand_path('../../../../javascript/webauthn_autofill.js', __FILE__)).freeze
8
8
 
9
+ translatable_method :webauthn_invalid_webauthn_id_message, "no webauthn key with given id found"
10
+
9
11
  route(:webauthn_autofill_js) do |r|
10
12
  before_webauthn_autofill_js_route
11
13
  r.get do
@@ -47,7 +49,11 @@ module Rodauth
47
49
  .where(webauthn_keys_webauthn_id_column => credential_id)
48
50
  .get(webauthn_keys_account_id_column)
49
51
 
50
- @account = account_ds(account_id).first if account_id
52
+ unless account_id
53
+ throw_error_reason(:invalid_webauthn_id, invalid_field_error_status, webauthn_auth_param, webauthn_invalid_webauthn_id_message)
54
+ end
55
+
56
+ @account = account_ds(account_id).first
51
57
  end
52
58
 
53
59
  def webauthn_login_options?
@@ -10,6 +10,11 @@ module Rodauth
10
10
 
11
11
  error_flash "There was an error authenticating via WebAuthn"
12
12
 
13
+ auth_value_method :webauthn_login_user_verification_additional_factor?, false
14
+
15
+ internal_request_method :webauthn_login_params
16
+ internal_request_method :webauthn_login
17
+
13
18
  route(:webauthn_login) do |r|
14
19
  check_already_logged_in
15
20
  before_webauthn_login_route
@@ -24,6 +29,9 @@ module Rodauth
24
29
  before_webauthn_login
25
30
  login('webauthn') do
26
31
  webauthn_update_session(webauthn_credential.id)
32
+ if webauthn_login_verification_factor?(webauthn_credential)
33
+ two_factor_update_session('webauthn-verification')
34
+ end
27
35
  end
28
36
  end
29
37
 
@@ -48,12 +56,23 @@ module Rodauth
48
56
  end
49
57
  end
50
58
 
59
+ def webauthn_user_verification
60
+ return 'preferred' if webauthn_login_user_verification_additional_factor?
61
+ super
62
+ end
63
+
51
64
  def use_multi_phase_login?
52
65
  true
53
66
  end
54
67
 
55
68
  private
56
69
 
70
+ def webauthn_login_verification_factor?(webauthn_credential)
71
+ webauthn_login_user_verification_additional_factor? &&
72
+ webauthn_credential.response.authenticator_data.user_verified? &&
73
+ uses_two_factor_authentication?
74
+ end
75
+
57
76
  def account_from_webauthn_login
58
77
  account_from_login(param(login_param))
59
78
  end
@@ -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 = 30
9
+ MINOR = 31
10
10
 
11
11
  # The patch version of Rodauth, updated only for bug fixes from the last
12
12
  # feature release.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.30.0
4
+ version: 2.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-22 00:00:00.000000000 Z
11
+ date: 2023-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -348,6 +348,7 @@ extra_rdoc_files:
348
348
  - doc/release_notes/2.29.0.txt
349
349
  - doc/release_notes/2.3.0.txt
350
350
  - doc/release_notes/2.30.0.txt
351
+ - doc/release_notes/2.31.0.txt
351
352
  - doc/release_notes/2.4.0.txt
352
353
  - doc/release_notes/2.5.0.txt
353
354
  - doc/release_notes/2.6.0.txt
@@ -394,6 +395,7 @@ files:
394
395
  - doc/guides/query_params.rdoc
395
396
  - doc/guides/redirects.rdoc
396
397
  - doc/guides/registration_field.rdoc
398
+ - doc/guides/render_confirmation.rdoc
397
399
  - doc/guides/require_mfa.rdoc
398
400
  - doc/guides/reset_password_autologin.rdoc
399
401
  - doc/guides/share_configuration.rdoc
@@ -465,6 +467,7 @@ files:
465
467
  - doc/release_notes/2.29.0.txt
466
468
  - doc/release_notes/2.3.0.txt
467
469
  - doc/release_notes/2.30.0.txt
470
+ - doc/release_notes/2.31.0.txt
468
471
  - doc/release_notes/2.4.0.txt
469
472
  - doc/release_notes/2.5.0.txt
470
473
  - doc/release_notes/2.6.0.txt