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