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.
- checksums.yaml +4 -4
- data/CHANGELOG +36 -0
- data/README.rdoc +1 -1
- data/doc/argon2.rdoc +9 -5
- data/doc/base.rdoc +1 -0
- data/doc/change_login.rdoc +1 -0
- data/doc/change_password.rdoc +1 -0
- data/doc/close_account.rdoc +1 -0
- data/doc/confirm_password.rdoc +1 -0
- data/doc/create_account.rdoc +1 -0
- data/doc/email_auth.rdoc +1 -0
- data/doc/jwt.rdoc +1 -0
- data/doc/lockout.rdoc +4 -2
- data/doc/login.rdoc +2 -1
- data/doc/logout.rdoc +1 -0
- data/doc/otp.rdoc +3 -0
- data/doc/release_notes/2.32.0.txt +65 -0
- data/doc/release_notes/2.33.0.txt +18 -0
- data/doc/remember.rdoc +1 -0
- data/doc/reset_password.rdoc +2 -0
- data/doc/sms_codes.rdoc +7 -0
- data/doc/two_factor_base.rdoc +2 -0
- data/doc/verify_account.rdoc +2 -0
- data/doc/verify_login_change.rdoc +1 -0
- data/doc/webauthn.rdoc +2 -0
- data/lib/rodauth/features/active_sessions.rb +10 -4
- data/lib/rodauth/features/argon2.rb +26 -6
- data/lib/rodauth/features/base.rb +39 -4
- data/lib/rodauth/features/change_login.rb +2 -2
- data/lib/rodauth/features/change_password.rb +2 -2
- data/lib/rodauth/features/close_account.rb +2 -2
- data/lib/rodauth/features/confirm_password.rb +2 -2
- data/lib/rodauth/features/create_account.rb +3 -3
- data/lib/rodauth/features/email_auth.rb +13 -20
- data/lib/rodauth/features/email_base.rb +4 -6
- data/lib/rodauth/features/jwt.rb +17 -1
- data/lib/rodauth/features/jwt_refresh.rb +4 -2
- data/lib/rodauth/features/lockout.rb +12 -14
- data/lib/rodauth/features/login.rb +13 -4
- data/lib/rodauth/features/logout.rb +2 -2
- data/lib/rodauth/features/otp.rb +35 -7
- data/lib/rodauth/features/remember.rb +15 -11
- data/lib/rodauth/features/reset_password.rb +11 -13
- data/lib/rodauth/features/single_session.rb +4 -3
- data/lib/rodauth/features/sms_codes.rb +42 -13
- data/lib/rodauth/features/two_factor_base.rb +8 -6
- data/lib/rodauth/features/update_password_hash.rb +2 -1
- data/lib/rodauth/features/verify_account.rb +13 -20
- data/lib/rodauth/features/verify_login_change.rb +8 -10
- data/lib/rodauth/features/webauthn.rb +6 -6
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +16 -0
- 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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
251
|
-
redirect_two_factor_authenticated
|
252
|
+
require_response(:_two_factor_auth_response)
|
252
253
|
end
|
253
254
|
|
254
|
-
def
|
255
|
+
def _two_factor_auth_response
|
255
256
|
saved_two_factor_auth_redirect = remove_session_value(two_factor_auth_redirect_session_key)
|
256
|
-
|
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
|
-
|
83
|
+
verify_account_email_sent_response
|
83
84
|
end
|
84
85
|
end
|
85
86
|
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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),
|
data/lib/rodauth/version.rb
CHANGED
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.
|
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-
|
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
|