rodauth 2.31.0 → 2.33.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +36 -0
  3. data/README.rdoc +1 -1
  4. data/doc/argon2.rdoc +9 -5
  5. data/doc/base.rdoc +1 -0
  6. data/doc/change_login.rdoc +1 -0
  7. data/doc/change_password.rdoc +1 -0
  8. data/doc/close_account.rdoc +1 -0
  9. data/doc/confirm_password.rdoc +1 -0
  10. data/doc/create_account.rdoc +1 -0
  11. data/doc/email_auth.rdoc +1 -0
  12. data/doc/jwt.rdoc +1 -0
  13. data/doc/lockout.rdoc +4 -2
  14. data/doc/login.rdoc +2 -1
  15. data/doc/logout.rdoc +1 -0
  16. data/doc/otp.rdoc +3 -0
  17. data/doc/release_notes/2.32.0.txt +65 -0
  18. data/doc/release_notes/2.33.0.txt +18 -0
  19. data/doc/remember.rdoc +1 -0
  20. data/doc/reset_password.rdoc +2 -0
  21. data/doc/sms_codes.rdoc +7 -0
  22. data/doc/two_factor_base.rdoc +2 -0
  23. data/doc/verify_account.rdoc +2 -0
  24. data/doc/verify_login_change.rdoc +1 -0
  25. data/doc/webauthn.rdoc +2 -0
  26. data/lib/rodauth/features/active_sessions.rb +10 -4
  27. data/lib/rodauth/features/argon2.rb +26 -6
  28. data/lib/rodauth/features/base.rb +39 -4
  29. data/lib/rodauth/features/change_login.rb +2 -2
  30. data/lib/rodauth/features/change_password.rb +2 -2
  31. data/lib/rodauth/features/close_account.rb +2 -2
  32. data/lib/rodauth/features/confirm_password.rb +2 -2
  33. data/lib/rodauth/features/create_account.rb +3 -3
  34. data/lib/rodauth/features/email_auth.rb +13 -20
  35. data/lib/rodauth/features/email_base.rb +4 -6
  36. data/lib/rodauth/features/jwt.rb +17 -1
  37. data/lib/rodauth/features/jwt_refresh.rb +4 -2
  38. data/lib/rodauth/features/lockout.rb +12 -14
  39. data/lib/rodauth/features/login.rb +13 -4
  40. data/lib/rodauth/features/logout.rb +2 -2
  41. data/lib/rodauth/features/otp.rb +35 -7
  42. data/lib/rodauth/features/remember.rb +15 -11
  43. data/lib/rodauth/features/reset_password.rb +11 -13
  44. data/lib/rodauth/features/single_session.rb +4 -3
  45. data/lib/rodauth/features/sms_codes.rb +42 -13
  46. data/lib/rodauth/features/two_factor_base.rb +8 -6
  47. data/lib/rodauth/features/update_password_hash.rb +2 -1
  48. data/lib/rodauth/features/verify_account.rb +13 -20
  49. data/lib/rodauth/features/verify_login_change.rb +8 -10
  50. data/lib/rodauth/features/webauthn.rb +6 -6
  51. data/lib/rodauth/version.rb +1 -1
  52. data/lib/rodauth.rb +16 -0
  53. metadata +6 -2
@@ -55,6 +55,10 @@ module Rodauth
55
55
  redirect(:sms_request){sms_request_path}
56
56
  redirect(:sms_lockout){two_factor_auth_required_redirect}
57
57
 
58
+ response :sms_confirm
59
+ response :sms_disable
60
+ response :sms_needs_confirmation
61
+
58
62
  loaded_templates %w'sms-auth sms-confirm sms-disable sms-request sms-setup sms-code-field password-field'
59
63
  view 'sms-auth', 'Authenticate via SMS Code', 'sms_auth'
60
64
  view 'sms-confirm', 'Confirm SMS Backup Number', 'sms_confirm'
@@ -72,6 +76,7 @@ module Rodauth
72
76
  auth_value_method :sms_code_param, 'sms-code'
73
77
  auth_value_method :sms_codes_table, :account_sms_codes
74
78
  auth_value_method :sms_confirm_code_length, 12
79
+ auth_value_method :sms_confirm_deadline, 86400
75
80
  auth_value_method :sms_failure_limit, 5
76
81
  auth_value_method :sms_failures_column, :num_failures
77
82
  auth_value_method :sms_id_column, :id
@@ -86,7 +91,11 @@ module Rodauth
86
91
 
87
92
  auth_cached_method :sms
88
93
 
89
- auth_value_methods :sms_codes_primary?
94
+ auth_value_methods(
95
+ :sms_codes_primary?,
96
+ :sms_needs_confirmation_notice_flash,
97
+ :sms_request_response
98
+ )
90
99
 
91
100
  auth_methods(
92
101
  :sms_auth_message,
@@ -104,6 +113,7 @@ module Rodauth
104
113
  :sms_new_confirm_code,
105
114
  :sms_normalize_phone,
106
115
  :sms_record_failure,
116
+ :sms_remove_expired_confirm_code,
107
117
  :sms_remove_failures,
108
118
  :sms_send,
109
119
  :sms_set_code,
@@ -136,9 +146,8 @@ module Rodauth
136
146
  sms_send_auth_code
137
147
  after_sms_request
138
148
  end
139
-
140
- set_notice_flash sms_request_notice_flash
141
- redirect sms_auth_redirect
149
+
150
+ require_response(:_sms_request_response)
142
151
  end
143
152
  end
144
153
 
@@ -189,6 +198,7 @@ module Rodauth
189
198
  require_two_factor_setup
190
199
  require_two_factor_authenticated
191
200
  end
201
+ sms_remove_expired_confirm_code
192
202
  require_sms_not_setup
193
203
 
194
204
  if sms_needs_confirmation?
@@ -223,8 +233,7 @@ module Rodauth
223
233
  after_sms_setup
224
234
  end
225
235
 
226
- set_notice_flash sms_needs_confirmation_error_flash
227
- redirect sms_needs_confirmation_redirect
236
+ sms_needs_confirmation_response
228
237
  end
229
238
 
230
239
  set_error_flash sms_setup_error_flash
@@ -238,6 +247,7 @@ module Rodauth
238
247
  require_two_factor_setup
239
248
  require_two_factor_authenticated
240
249
  end
250
+ sms_remove_expired_confirm_code
241
251
  require_sms_not_setup
242
252
  before_sms_confirm_route
243
253
 
@@ -256,8 +266,7 @@ module Rodauth
256
266
  end
257
267
  end
258
268
 
259
- set_notice_flash sms_confirm_notice_flash
260
- redirect sms_confirm_redirect
269
+ sms_confirm_response
261
270
  end
262
271
 
263
272
  sms_confirm_failure
@@ -287,8 +296,7 @@ module Rodauth
287
296
  end
288
297
  after_sms_disable
289
298
  end
290
- set_notice_flash sms_disable_notice_flash
291
- redirect sms_disable_redirect
299
+ sms_disable_response
292
300
  end
293
301
 
294
302
  set_response_error_reason_status(:invalid_password, invalid_password_error_status)
@@ -358,16 +366,17 @@ module Rodauth
358
366
  def sms_setup(phone_number)
359
367
  # Cannot handle uniqueness violation here, as the phone number given may not match the
360
368
  # one in the table.
361
- sms_ds.insert(sms_id_column=>session_value, sms_phone_column=>phone_number)
369
+ sms_ds.insert(sms_id_column=>session_value, sms_phone_column=>phone_number, sms_failures_column => nil)
362
370
  remove_instance_variable(:@sms) if instance_variable_defined?(:@sms)
363
371
  end
364
372
 
365
373
  def sms_remove_failures
366
- update_sms(sms_failures_column => 0, sms_code_column => nil)
374
+ return if sms_needs_confirmation?
375
+ update_hash_ds(sms, sms_ds.exclude(sms_failures_column => nil), sms_failures_column => 0, sms_code_column => nil)
367
376
  end
368
377
 
369
378
  def sms_confirm
370
- sms_remove_failures
379
+ update_hash_ds(sms, sms_ds.where(sms_failures_column => nil), sms_failures_column => 0, sms_code_column => nil)
371
380
  super if defined?(super)
372
381
  end
373
382
 
@@ -395,10 +404,21 @@ module Rodauth
395
404
  "SMS confirmation code for #{domain} is #{code}"
396
405
  end
397
406
 
407
+ def sms_needs_confirmation_notice_flash
408
+ sms_needs_confirmation_error_flash
409
+ end
410
+
398
411
  def sms_set_code(code)
399
412
  update_sms(sms_code_column=>code, sms_issued_at_column=>Sequel::CURRENT_TIMESTAMP)
400
413
  end
401
414
 
415
+ def sms_remove_expired_confirm_code
416
+ db[sms_codes_table].
417
+ where(sms_id_column=>session_value, sms_failures_column => nil).
418
+ where(Sequel[sms_issued_at_column] < Sequel.date_sub(Sequel::CURRENT_TIMESTAMP, seconds: sms_confirm_deadline)).
419
+ delete
420
+ end
421
+
402
422
  def sms_record_failure
403
423
  update_sms(sms_failures_column=>Sequel.expr(sms_failures_column)+1)
404
424
  sms[sms_failures_column] = sms_ds.get(sms_failures_column)
@@ -449,6 +469,11 @@ module Rodauth
449
469
 
450
470
  private
451
471
 
472
+ def _sms_request_response
473
+ set_notice_flash sms_request_notice_flash
474
+ redirect sms_auth_redirect
475
+ end
476
+
452
477
  def _two_factor_auth_links
453
478
  links = super
454
479
  links << [30, sms_request_path, sms_auth_link_text] if sms_available?
@@ -503,5 +528,9 @@ module Rodauth
503
528
  def sms_ds
504
529
  db[sms_codes_table].where(sms_id_column=>session_value)
505
530
  end
531
+
532
+ def use_date_arithmetic?
533
+ true
534
+ end
506
535
  end
507
536
  end
@@ -23,6 +23,8 @@ module Rodauth
23
23
  redirect(:two_factor_need_setup){two_factor_manage_path}
24
24
  redirect(:two_factor_auth_required){two_factor_auth_path}
25
25
 
26
+ response :two_factor_disable
27
+
26
28
  notice_flash "You have been multifactor authenticated", "two_factor_auth"
27
29
  notice_flash "All multifactor authentication methods have been disabled", "two_factor_disable"
28
30
 
@@ -55,6 +57,7 @@ module Rodauth
55
57
 
56
58
  auth_private_methods(
57
59
  :two_factor_auth_links,
60
+ :two_factor_auth_response,
58
61
  :two_factor_setup_links,
59
62
  :two_factor_remove_links
60
63
  )
@@ -106,8 +109,7 @@ module Rodauth
106
109
  _two_factor_remove_all_from_session
107
110
  after_two_factor_disable
108
111
  end
109
- set_notice_flash two_factor_disable_notice_flash
110
- redirect two_factor_disable_redirect
112
+ two_factor_disable_response
111
113
  end
112
114
 
113
115
  set_response_error_reason_status(:invalid_password, invalid_password_error_status)
@@ -247,13 +249,13 @@ module Rodauth
247
249
  two_factor_update_session(type)
248
250
  two_factor_remove_auth_failures
249
251
  after_two_factor_authentication
250
- set_notice_flash two_factor_auth_notice_flash
251
- redirect_two_factor_authenticated
252
+ require_response(:_two_factor_auth_response)
252
253
  end
253
254
 
254
- def redirect_two_factor_authenticated
255
+ def _two_factor_auth_response
255
256
  saved_two_factor_auth_redirect = remove_session_value(two_factor_auth_redirect_session_key)
256
- redirect saved_two_factor_auth_redirect || two_factor_auth_redirect
257
+ set_notice_flash two_factor_auth_notice_flash
258
+ redirect(saved_two_factor_auth_redirect || two_factor_auth_redirect)
257
259
  end
258
260
 
259
261
  def two_factor_remove_session(type)
@@ -6,6 +6,7 @@ module Rodauth
6
6
 
7
7
  def password_match?(password)
8
8
  if (result = super) && update_password_hash?
9
+ @update_password_hash = false
9
10
  set_password(password)
10
11
  end
11
12
 
@@ -15,7 +16,7 @@ module Rodauth
15
16
  private
16
17
 
17
18
  def update_password_hash?
18
- password_hash_cost != @current_password_hash_cost
19
+ password_hash_cost != @current_password_hash_cost || @update_password_hash
19
20
  end
20
21
 
21
22
  def get_password_hash
@@ -24,6 +24,8 @@ module Rodauth
24
24
  button 'Verify Account'
25
25
  button 'Send Verification Email Again', 'verify_account_resend'
26
26
  redirect
27
+ response
28
+ response :verify_account_email_sent
27
29
  redirect(:verify_account_email_sent){default_post_email_redirect}
28
30
  redirect(:verify_account_email_recently_sent){default_post_email_redirect}
29
31
  email :verify_account, 'Verify Account'
@@ -69,7 +71,6 @@ module Rodauth
69
71
  end
70
72
 
71
73
  r.post do
72
- verified = false
73
74
  if account_from_login(param(login_param)) && allow_resending_verify_account_email?
74
75
  if verify_account_email_recently_sent?
75
76
  set_redirect_error_flash verify_account_email_recently_sent_error_flash
@@ -79,18 +80,13 @@ module Rodauth
79
80
  before_verify_account_email_resend
80
81
  if verify_account_email_resend
81
82
  after_verify_account_email_resend
82
- verified = true
83
+ verify_account_email_sent_response
83
84
  end
84
85
  end
85
86
 
86
- if verified
87
- set_notice_flash verify_account_email_sent_notice_flash
88
- else
89
- set_redirect_error_status(no_matching_login_error_status)
90
- set_error_reason :no_matching_login
91
- set_redirect_error_flash verify_account_resend_error_flash
92
- end
93
-
87
+ set_redirect_error_status(no_matching_login_error_status)
88
+ set_error_reason :no_matching_login
89
+ set_redirect_error_flash verify_account_resend_error_flash
94
90
  redirect verify_account_email_sent_redirect
95
91
  end
96
92
  end
@@ -106,14 +102,12 @@ module Rodauth
106
102
  redirect(r.path)
107
103
  end
108
104
 
109
- if key = session[verify_account_session_key]
110
- if account_from_verify_account_key(key)
111
- verify_account_view
112
- else
113
- remove_session_value(verify_account_session_key)
114
- set_redirect_error_flash no_matching_verify_account_key_error_flash
115
- redirect require_login_redirect
116
- end
105
+ if (key = session[verify_account_session_key]) && account_from_verify_account_key(key)
106
+ verify_account_view
107
+ else
108
+ remove_session_value(verify_account_session_key)
109
+ set_redirect_error_flash no_matching_verify_account_key_error_flash
110
+ redirect require_login_redirect
117
111
  end
118
112
  end
119
113
 
@@ -154,8 +148,7 @@ module Rodauth
154
148
  end
155
149
 
156
150
  remove_session_value(verify_account_session_key)
157
- set_notice_flash verify_account_notice_flash
158
- redirect verify_account_redirect
151
+ verify_account_response
159
152
  end
160
153
 
161
154
  set_error_flash verify_account_error_flash
@@ -18,6 +18,7 @@ module Rodauth
18
18
  before 'verify_login_change_email'
19
19
  button 'Verify Login Change'
20
20
  redirect
21
+ response
21
22
  redirect(:verify_login_change_duplicate_account){require_login_redirect}
22
23
 
23
24
  auth_value_method :verify_login_change_autologin?, false
@@ -61,14 +62,12 @@ module Rodauth
61
62
  redirect(r.path)
62
63
  end
63
64
 
64
- if key = session[verify_login_change_session_key]
65
- if account_from_verify_login_change_key(key)
66
- verify_login_change_view
67
- else
68
- remove_session_value(verify_login_change_session_key)
69
- set_redirect_error_flash no_matching_verify_login_change_key_error_flash
70
- redirect require_login_redirect
71
- end
65
+ if (key = session[verify_login_change_session_key]) && account_from_verify_login_change_key(key)
66
+ verify_login_change_view
67
+ else
68
+ remove_session_value(verify_login_change_session_key)
69
+ set_redirect_error_flash no_matching_verify_login_change_key_error_flash
70
+ redirect require_login_redirect
72
71
  end
73
72
  end
74
73
 
@@ -98,8 +97,7 @@ module Rodauth
98
97
  end
99
98
 
100
99
  remove_session_value(verify_login_change_session_key)
101
- set_notice_flash verify_login_change_notice_flash
102
- redirect verify_login_change_redirect
100
+ verify_login_change_response
103
101
  end
104
102
  end
105
103
 
@@ -30,6 +30,8 @@ module Rodauth
30
30
 
31
31
  redirect :webauthn_setup
32
32
  redirect :webauthn_remove
33
+ response :webauthn_setup
34
+ response :webauthn_remove
33
35
 
34
36
  notice_flash "WebAuthn authentication is now setup", 'webauthn_setup'
35
37
  notice_flash "WebAuthn authenticator has been removed", 'webauthn_remove'
@@ -194,8 +196,7 @@ module Rodauth
194
196
  throw_error_reason(:duplicate_webauthn_id, invalid_field_error_status, webauthn_setup_param, webauthn_duplicate_webauthn_id_message)
195
197
  end
196
198
 
197
- set_notice_flash webauthn_setup_notice_flash
198
- redirect webauthn_setup_redirect
199
+ webauthn_setup_response
199
200
  end
200
201
 
201
202
  set_error_flash webauthn_setup_error_flash
@@ -235,8 +236,7 @@ module Rodauth
235
236
  after_webauthn_remove
236
237
  end
237
238
 
238
- set_notice_flash webauthn_remove_notice_flash
239
- redirect webauthn_remove_redirect
239
+ webauthn_remove_response
240
240
  end
241
241
 
242
242
  set_error_flash webauthn_remove_error_flash
@@ -320,7 +320,7 @@ module Rodauth
320
320
 
321
321
  (challenge = param_or_nil(webauthn_setup_challenge_param)) &&
322
322
  (hmac = param_or_nil(webauthn_setup_challenge_hmac_param)) &&
323
- timing_safe_eql?(compute_hmac(challenge), hmac) &&
323
+ (timing_safe_eql?(compute_hmac(challenge), hmac) || (hmac_secret_rotation? && timing_safe_eql?(compute_old_hmac(challenge), hmac))) &&
324
324
  webauthn_credential.verify(challenge)
325
325
  end
326
326
 
@@ -376,7 +376,7 @@ module Rodauth
376
376
 
377
377
  (challenge = param_or_nil(webauthn_auth_challenge_param)) &&
378
378
  (hmac = param_or_nil(webauthn_auth_challenge_hmac_param)) &&
379
- timing_safe_eql?(compute_hmac(challenge), hmac) &&
379
+ (timing_safe_eql?(compute_hmac(challenge), hmac) || (hmac_secret_rotation? && timing_safe_eql?(compute_old_hmac(challenge), hmac))) &&
380
380
  webauthn_credential.verify(challenge, public_key: pub_key, sign_count: sign_count) &&
381
381
  ds.update(
382
382
  webauthn_keys_sign_count_column => Integer(webauthn_credential.sign_count),
@@ -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 = 31
9
+ MINOR = 33
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
@@ -214,6 +214,22 @@ module Rodauth
214
214
  auth_methods meth
215
215
  end
216
216
 
217
+ def response(name=feature_name)
218
+ meth = :"#{name}_response"
219
+ overridable_meth = :"_#{meth}"
220
+ notice_flash_meth = :"#{name}_notice_flash"
221
+ redirect_meth = :"#{name}_redirect"
222
+ define_method(overridable_meth) do
223
+ set_notice_flash send(notice_flash_meth)
224
+ redirect send(redirect_meth)
225
+ end
226
+ define_method(meth) do
227
+ require_response(overridable_meth)
228
+ end
229
+ private overridable_meth, meth
230
+ auth_private_methods meth
231
+ end
232
+
217
233
  def loaded_templates(v)
218
234
  define_method(:loaded_templates) do
219
235
  super().concat(v)
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.31.0
4
+ version: 2.33.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-08-22 00:00:00.000000000 Z
11
+ date: 2023-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -349,6 +349,8 @@ extra_rdoc_files:
349
349
  - doc/release_notes/2.3.0.txt
350
350
  - doc/release_notes/2.30.0.txt
351
351
  - doc/release_notes/2.31.0.txt
352
+ - doc/release_notes/2.32.0.txt
353
+ - doc/release_notes/2.33.0.txt
352
354
  - doc/release_notes/2.4.0.txt
353
355
  - doc/release_notes/2.5.0.txt
354
356
  - doc/release_notes/2.6.0.txt
@@ -468,6 +470,8 @@ files:
468
470
  - doc/release_notes/2.3.0.txt
469
471
  - doc/release_notes/2.30.0.txt
470
472
  - doc/release_notes/2.31.0.txt
473
+ - doc/release_notes/2.32.0.txt
474
+ - doc/release_notes/2.33.0.txt
471
475
  - doc/release_notes/2.4.0.txt
472
476
  - doc/release_notes/2.5.0.txt
473
477
  - doc/release_notes/2.6.0.txt