rodauth 2.31.0 → 2.33.0

Sign up to get free protection for your applications and to get access to all the features.
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