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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rodauth/features/active_sessions.rb +2 -2
  3. data/lib/rodauth/features/argon2.rb +6 -6
  4. data/lib/rodauth/features/audit_logging.rb +4 -3
  5. data/lib/rodauth/features/base.rb +84 -30
  6. data/lib/rodauth/features/change_password.rb +11 -2
  7. data/lib/rodauth/features/confirm_password.rb +1 -1
  8. data/lib/rodauth/features/create_account.rb +2 -1
  9. data/lib/rodauth/features/disallow_password_reuse.rb +2 -2
  10. data/lib/rodauth/features/email_auth.rb +13 -4
  11. data/lib/rodauth/features/email_base.rb +36 -1
  12. data/lib/rodauth/features/http_basic_auth.rb +6 -1
  13. data/lib/rodauth/features/internal_request.rb +4 -0
  14. data/lib/rodauth/features/json.rb +5 -1
  15. data/lib/rodauth/features/jwt.rb +9 -3
  16. data/lib/rodauth/features/jwt_cors.rb +2 -0
  17. data/lib/rodauth/features/jwt_refresh.rb +33 -18
  18. data/lib/rodauth/features/lockout.rb +24 -21
  19. data/lib/rodauth/features/login.rb +12 -4
  20. data/lib/rodauth/features/login_password_requirements_base.rb +43 -13
  21. data/lib/rodauth/features/otp.rb +21 -12
  22. data/lib/rodauth/features/otp_unlock.rb +2 -2
  23. data/lib/rodauth/features/password_complexity.rb +2 -2
  24. data/lib/rodauth/features/password_expiration.rb +0 -1
  25. data/lib/rodauth/features/password_grace_period.rb +8 -0
  26. data/lib/rodauth/features/password_pepper.rb +11 -1
  27. data/lib/rodauth/features/recovery_codes.rb +4 -1
  28. data/lib/rodauth/features/remember.rb +4 -2
  29. data/lib/rodauth/features/require_domain.rb +12 -0
  30. data/lib/rodauth/features/require_hmac_secret.rb +12 -0
  31. data/lib/rodauth/features/reset_password.rb +25 -20
  32. data/lib/rodauth/features/single_session.rb +6 -2
  33. data/lib/rodauth/features/sms_codes.rb +4 -1
  34. data/lib/rodauth/features/two_factor_base.rb +2 -1
  35. data/lib/rodauth/features/update_password_hash.rb +5 -1
  36. data/lib/rodauth/features/verify_account.rb +29 -20
  37. data/lib/rodauth/features/verify_login_change.rb +19 -11
  38. data/lib/rodauth/features/webauthn.rb +16 -11
  39. data/lib/rodauth/features/webauthn_login.rb +1 -10
  40. data/lib/rodauth/features/webauthn_verify_account.rb +0 -4
  41. data/lib/rodauth/version.rb +1 -1
  42. data/lib/rodauth.rb +14 -12
  43. 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
- set_session_value(two_factor_auth_redirect_session_key, request.fullpath)
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
- set_password(password)
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 = session[verify_account_session_key] || param(verify_account_key_param)
118
- unless account_from_verify_account_key(key)
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
- if verify_account_set_password?
127
- password = param(password_param)
121
+ transaction do
122
+ handle_invalid_verify_account_key unless key && account_from_verify_account_key(key)
128
123
 
129
- if require_password_confirmation? && password != param(password_confirm_param)
130
- throw_error_reason(:passwords_do_not_match, unmatched_field_error_status, password_param, passwords_do_not_match_message)
131
- end
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
- unless password_meets_requirements?(password)
134
- throw_error_status(invalid_field_error_status, password_param, password_does_not_meet_requirements_message)
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
- update_account(account_status_column=>account_open_status_value) == 1
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
- @account = _account_from_verify_account_key(key)
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 = session[verify_login_change_session_key] || param(verify_login_change_key_param)
78
- unless account_from_verify_login_change_key(key)
79
- set_redirect_error_status(invalid_key_error_status)
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
- @account = _account_from_verify_login_change_key(key)
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).get([verify_login_change_login_column, verify_login_change_key_column])
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 two_factor_login_type_match?('webauthn')
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
- unless two_factor_authenticated?
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 two_factor_login_type_match?('webauthn')
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
- # :nocov:
293
+ # simplecov:disable
294
294
  raise e unless webauthn_id = webauthn_user_ids_ds.get(webauthn_user_ids_webauthn_id_column)
295
- # :nocov:
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
- # :nocov:
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
- # :nocov:
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
- # :nocov:
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
- # :nocov:
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
- session_value
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
@@ -40,9 +40,5 @@ module Rodauth
40
40
  @webauthn_credential = webauthn_setup_credential_from_form_submission
41
41
  add_webauthn_credential(@webauthn_credential)
42
42
  end
43
-
44
- def webauthn_account_id
45
- super || account_id
46
- end
47
43
  end
48
44
  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 = 46
9
+ MINOR = 48
10
10
 
11
11
  # The patch version of Rodauth, updated only for bug fixes from the last
12
12
  # feature release.
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
- # :nocov:
41
+ # simplecov:disable
40
42
  app.plugin :csrf
41
- # :nocov:
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
- # :nocov:
189
+ # simplecov:disable
188
190
  if constant
189
- # :nocov:
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
- # :nocov:
214
+ # simplecov:disable
213
215
  DEPRECATED_ARGS = []
214
- # :nocov:
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
- # :nocov:
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
- # :nocov:
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
- # :nocov:
380
+ # simplecov:disable
379
381
  # Only for backwards compatibility
380
382
  # RODAUTH3: Remove
381
383
  apply(&block) if block
382
- # :nocov:
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
- # :nocov:
474
+ # simplecov:disable
473
475
  else
474
476
  def make_shape_friendly # :nodoc:
475
477
  end
476
478
  end
477
- # :nocov:
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.46.0
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.16
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: []