rodauth 2.2.0 → 2.7.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 +50 -0
- data/README.rdoc +14 -0
- data/doc/base.rdoc +3 -1
- data/doc/jwt_refresh.rdoc +13 -0
- data/doc/login.rdoc +8 -0
- data/doc/login_password_requirements_base.rdoc +3 -0
- data/doc/password_pepper.rdoc +44 -0
- data/doc/recovery_codes.rdoc +2 -1
- data/doc/release_notes/2.3.0.txt +37 -0
- data/doc/release_notes/2.4.0.txt +22 -0
- data/doc/release_notes/2.5.0.txt +20 -0
- data/doc/release_notes/2.6.0.txt +37 -0
- data/doc/release_notes/2.7.0.txt +33 -0
- data/doc/remember.rdoc +1 -1
- data/doc/verify_login_change.rdoc +1 -0
- data/javascript/webauthn_auth.js +9 -9
- data/javascript/webauthn_setup.js +9 -6
- data/lib/rodauth.rb +14 -6
- data/lib/rodauth/features/base.rb +19 -4
- data/lib/rodauth/features/change_password.rb +1 -1
- data/lib/rodauth/features/close_account.rb +8 -6
- data/lib/rodauth/features/confirm_password.rb +2 -2
- data/lib/rodauth/features/disallow_password_reuse.rb +4 -2
- data/lib/rodauth/features/email_auth.rb +1 -1
- data/lib/rodauth/features/jwt.rb +11 -3
- data/lib/rodauth/features/jwt_refresh.rb +70 -8
- data/lib/rodauth/features/login.rb +23 -12
- data/lib/rodauth/features/login_password_requirements_base.rb +9 -4
- data/lib/rodauth/features/otp.rb +0 -2
- data/lib/rodauth/features/password_pepper.rb +45 -0
- data/lib/rodauth/features/recovery_codes.rb +22 -1
- data/lib/rodauth/features/remember.rb +6 -1
- data/lib/rodauth/features/session_expiration.rb +1 -6
- data/lib/rodauth/features/verify_account.rb +6 -7
- data/lib/rodauth/features/verify_login_change.rb +2 -1
- data/lib/rodauth/features/webauthn_login.rb +1 -1
- data/lib/rodauth/migrations.rb +16 -5
- data/lib/rodauth/version.rb +1 -1
- metadata +16 -3
@@ -4,8 +4,10 @@ module Rodauth
|
|
4
4
|
Feature.define(:login_password_requirements_base, :LoginPasswordRequirementsBase) do
|
5
5
|
translatable_method :already_an_account_with_this_login_message, 'already an account with this login'
|
6
6
|
auth_value_method :login_confirm_param, 'login-confirm'
|
7
|
+
auth_value_method :login_email_regexp, /\A[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+\z/
|
7
8
|
auth_value_method :login_minimum_length, 3
|
8
9
|
auth_value_method :login_maximum_length, 255
|
10
|
+
translatable_method :login_not_valid_email_message, 'not a valid email address'
|
9
11
|
translatable_method :logins_do_not_match_message, 'logins do not match'
|
10
12
|
auth_value_method :password_confirm_param, 'password-confirm'
|
11
13
|
auth_value_method :password_minimum_length, 6
|
@@ -28,6 +30,7 @@ module Rodauth
|
|
28
30
|
|
29
31
|
auth_methods(
|
30
32
|
:login_meets_requirements?,
|
33
|
+
:login_valid_email?,
|
31
34
|
:password_hash,
|
32
35
|
:password_meets_requirements?,
|
33
36
|
:set_password
|
@@ -104,13 +107,15 @@ module Rodauth
|
|
104
107
|
|
105
108
|
def login_meets_email_requirements?(login)
|
106
109
|
return true unless require_email_address_logins?
|
107
|
-
if login
|
108
|
-
|
109
|
-
end
|
110
|
-
@login_requirement_message = 'not a valid email address'
|
110
|
+
return true if login_valid_email?(login)
|
111
|
+
@login_requirement_message = login_not_valid_email_message
|
111
112
|
return false
|
112
113
|
end
|
113
114
|
|
115
|
+
def login_valid_email?(login)
|
116
|
+
login =~ login_email_regexp
|
117
|
+
end
|
118
|
+
|
114
119
|
def password_meets_length_requirements?(password)
|
115
120
|
return true if password_minimum_length <= password.length
|
116
121
|
@password_requirement_message = password_too_short_message
|
data/lib/rodauth/features/otp.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
Feature.define(:password_pepper, :PasswordPepper) do
|
5
|
+
depends :login_password_requirements_base
|
6
|
+
|
7
|
+
auth_value_method :password_pepper, nil
|
8
|
+
auth_value_method :previous_password_peppers, [""]
|
9
|
+
auth_value_method :password_pepper_update?, true
|
10
|
+
|
11
|
+
def password_match?(password)
|
12
|
+
if (result = super) && @previous_pepper_matched && password_pepper_update?
|
13
|
+
set_password(password)
|
14
|
+
end
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def password_hash(password)
|
22
|
+
super(password + password_pepper.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def password_hash_match?(hash, password)
|
26
|
+
return super if password_pepper.nil?
|
27
|
+
|
28
|
+
return true if super(hash, password + password_pepper)
|
29
|
+
|
30
|
+
@previous_pepper_matched = previous_password_peppers.any? do |pepper|
|
31
|
+
super(hash, password + pepper)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def database_function_password_match?(name, hash_id, password, salt)
|
36
|
+
return super if password_pepper.nil?
|
37
|
+
|
38
|
+
return true if super(name, hash_id, password + password_pepper, salt)
|
39
|
+
|
40
|
+
@previous_pepper_matched = previous_password_peppers.any? do |pepper|
|
41
|
+
super(name, hash_id, password + pepper, salt)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -34,6 +34,7 @@ module Rodauth
|
|
34
34
|
auth_value_method :add_recovery_codes_param, 'add'
|
35
35
|
translatable_method :add_recovery_codes_heading, '<h2>Add Additional Recovery Codes</h2>'
|
36
36
|
auth_value_method :auto_add_recovery_codes?, false
|
37
|
+
auth_value_method :auto_remove_recovery_codes?, false
|
37
38
|
translatable_method :invalid_recovery_code_message, "Invalid recovery code"
|
38
39
|
auth_value_method :recovery_codes_limit, 16
|
39
40
|
auth_value_method :recovery_codes_column, :code
|
@@ -56,7 +57,6 @@ module Rodauth
|
|
56
57
|
:can_add_recovery_codes?,
|
57
58
|
:new_recovery_code,
|
58
59
|
:recovery_code_match?,
|
59
|
-
:recovery_codes
|
60
60
|
)
|
61
61
|
|
62
62
|
route(:recovery_auth) do |r|
|
@@ -213,6 +213,21 @@ module Rodauth
|
|
213
213
|
super
|
214
214
|
end
|
215
215
|
|
216
|
+
def after_otp_disable
|
217
|
+
super if defined?(super)
|
218
|
+
auto_remove_recovery_codes
|
219
|
+
end
|
220
|
+
|
221
|
+
def after_sms_disable
|
222
|
+
super if defined?(super)
|
223
|
+
auto_remove_recovery_codes
|
224
|
+
end
|
225
|
+
|
226
|
+
def after_webauthn_remove
|
227
|
+
super if defined?(super)
|
228
|
+
auto_remove_recovery_codes
|
229
|
+
end
|
230
|
+
|
216
231
|
def new_recovery_code
|
217
232
|
random_key
|
218
233
|
end
|
@@ -227,6 +242,12 @@ module Rodauth
|
|
227
242
|
end
|
228
243
|
end
|
229
244
|
|
245
|
+
def auto_remove_recovery_codes
|
246
|
+
if auto_remove_recovery_codes? && (%w'totp webauthn sms_code' & possible_authentication_methods).empty?
|
247
|
+
recovery_codes_remove
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
230
251
|
def _recovery_codes
|
231
252
|
recovery_codes_ds.select_map(recovery_codes_column)
|
232
253
|
end
|
@@ -58,7 +58,9 @@ module Rodauth
|
|
58
58
|
if [remember_remember_param_value, remember_forget_param_value, remember_disable_param_value].include?(remember)
|
59
59
|
transaction do
|
60
60
|
before_remember
|
61
|
+
# :nocov:
|
61
62
|
case remember
|
63
|
+
# :nocov:
|
62
64
|
when remember_remember_param_value
|
63
65
|
remember_login
|
64
66
|
when remember_forget_param_value
|
@@ -130,11 +132,14 @@ module Rodauth
|
|
130
132
|
opts = Hash[remember_cookie_options]
|
131
133
|
opts[:value] = "#{account_id}_#{convert_token_key(remember_key_value)}"
|
132
134
|
opts[:expires] = convert_timestamp(active_remember_key_ds.get(remember_deadline_column))
|
135
|
+
opts[:path] = "/" unless opts.key?(:path)
|
133
136
|
::Rack::Utils.set_cookie_header!(response.headers, remember_cookie_key, opts)
|
134
137
|
end
|
135
138
|
|
136
139
|
def forget_login
|
137
|
-
|
140
|
+
opts = Hash[remember_cookie_options]
|
141
|
+
opts[:path] = "/" unless opts.key?(:path)
|
142
|
+
::Rack::Utils.delete_cookie_header!(response.headers, remember_cookie_key, opts)
|
138
143
|
end
|
139
144
|
|
140
145
|
def get_remember_key
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Rodauth
|
4
4
|
Feature.define(:session_expiration, :SessionExpiration) do
|
5
5
|
error_flash "This session has expired, please login again"
|
6
|
+
redirect{require_login_redirect}
|
6
7
|
|
7
8
|
auth_value_method :max_session_lifetime, 86400
|
8
9
|
session_key :session_created_session_key, :session_created_at
|
@@ -11,8 +12,6 @@ module Rodauth
|
|
11
12
|
auth_value_method :session_inactivity_timeout, 1800
|
12
13
|
session_key :session_last_activity_session_key, :last_session_activity_at
|
13
14
|
|
14
|
-
auth_value_methods :session_expiration_redirect
|
15
|
-
|
16
15
|
def check_session_expiration
|
17
16
|
return unless logged_in?
|
18
17
|
|
@@ -43,10 +42,6 @@ module Rodauth
|
|
43
42
|
redirect session_expiration_redirect
|
44
43
|
end
|
45
44
|
|
46
|
-
def session_expiration_redirect
|
47
|
-
require_login_redirect
|
48
|
-
end
|
49
|
-
|
50
45
|
def update_session
|
51
46
|
super
|
52
47
|
t = Time.now.to_i
|
@@ -47,7 +47,6 @@ module Rodauth
|
|
47
47
|
:get_verify_account_key,
|
48
48
|
:get_verify_account_email_last_sent,
|
49
49
|
:remove_verify_account_key,
|
50
|
-
:resend_verify_account_view,
|
51
50
|
:send_verify_account_email,
|
52
51
|
:set_verify_account_email_last_sent,
|
53
52
|
:verify_account,
|
@@ -245,6 +244,12 @@ module Rodauth
|
|
245
244
|
end
|
246
245
|
end
|
247
246
|
|
247
|
+
def setup_account_verification
|
248
|
+
generate_verify_account_key_value
|
249
|
+
create_verify_account_key
|
250
|
+
send_verify_account_email
|
251
|
+
end
|
252
|
+
|
248
253
|
private
|
249
254
|
|
250
255
|
def _login_form_footer_links
|
@@ -276,12 +281,6 @@ module Rodauth
|
|
276
281
|
super
|
277
282
|
end
|
278
283
|
|
279
|
-
def setup_account_verification
|
280
|
-
generate_verify_account_key_value
|
281
|
-
create_verify_account_key
|
282
|
-
send_verify_account_email
|
283
|
-
end
|
284
|
-
|
285
284
|
def verify_account_check_already_logged_in
|
286
285
|
check_already_logged_in
|
287
286
|
end
|
@@ -8,6 +8,7 @@ module Rodauth
|
|
8
8
|
error_flash "Unable to change login as there is already an account with the new login", 'verify_login_change_duplicate_account'
|
9
9
|
error_flash "There was an error verifying your login change: invalid verify login change key", 'no_matching_verify_login_change_key'
|
10
10
|
notice_flash "Your login change has been verified"
|
11
|
+
notice_flash "An email has been sent to you with a link to verify your login change", 'change_login_needs_verification'
|
11
12
|
loaded_templates %w'verify-login-change verify-login-change-email'
|
12
13
|
view 'verify-login-change', 'Verify Login Change'
|
13
14
|
additional_form_tags
|
@@ -131,7 +132,7 @@ module Rodauth
|
|
131
132
|
end
|
132
133
|
|
133
134
|
def change_login_notice_flash
|
134
|
-
|
135
|
+
change_login_needs_verification_notice_flash
|
135
136
|
end
|
136
137
|
|
137
138
|
def verify_login_change_old_login
|
data/lib/rodauth/migrations.rb
CHANGED
@@ -9,9 +9,14 @@ module Rodauth
|
|
9
9
|
case db.database_type
|
10
10
|
when :postgres
|
11
11
|
search_path = opts[:search_path] || 'public, pg_temp'
|
12
|
+
primary_key_type =
|
13
|
+
case db.schema(table_name).find { |row| row.first == :id }[1][:db_type]
|
14
|
+
when 'uuid' then :uuid
|
15
|
+
else :int8
|
16
|
+
end
|
12
17
|
|
13
18
|
db.run <<END
|
14
|
-
CREATE OR REPLACE FUNCTION #{get_salt_name}(acct_id
|
19
|
+
CREATE OR REPLACE FUNCTION #{get_salt_name}(acct_id #{primary_key_type}) RETURNS text AS $$
|
15
20
|
DECLARE salt text;
|
16
21
|
BEGIN
|
17
22
|
SELECT substr(password_hash, 0, 30) INTO salt
|
@@ -25,7 +30,7 @@ SET search_path = #{search_path};
|
|
25
30
|
END
|
26
31
|
|
27
32
|
db.run <<END
|
28
|
-
CREATE OR REPLACE FUNCTION #{valid_hash_name}(acct_id
|
33
|
+
CREATE OR REPLACE FUNCTION #{valid_hash_name}(acct_id #{primary_key_type}, hash text) RETURNS boolean AS $$
|
29
34
|
DECLARE valid boolean;
|
30
35
|
BEGIN
|
31
36
|
SELECT password_hash = hash INTO valid
|
@@ -100,13 +105,19 @@ END
|
|
100
105
|
end
|
101
106
|
|
102
107
|
def self.drop_database_authentication_functions(db, opts={})
|
108
|
+
table_name = opts[:table_name] || :account_password_hashes
|
103
109
|
get_salt_name = opts[:get_salt_name] || :rodauth_get_salt
|
104
110
|
valid_hash_name = opts[:valid_hash_name] || :rodauth_valid_password_hash
|
105
111
|
|
106
112
|
case db.database_type
|
107
113
|
when :postgres
|
108
|
-
|
109
|
-
|
114
|
+
primary_key_type =
|
115
|
+
case db.schema(table_name).find { |row| row.first == :id }[1][:db_type]
|
116
|
+
when 'uuid' then :uuid
|
117
|
+
else :int8
|
118
|
+
end
|
119
|
+
db.run "DROP FUNCTION #{get_salt_name}(#{primary_key_type})"
|
120
|
+
db.run "DROP FUNCTION #{valid_hash_name}(#{primary_key_type}, text)"
|
110
121
|
when :mysql, :mssql
|
111
122
|
db.run "DROP FUNCTION #{get_salt_name}"
|
112
123
|
db.run "DROP FUNCTION #{valid_hash_name}"
|
@@ -118,6 +129,6 @@ END
|
|
118
129
|
end
|
119
130
|
|
120
131
|
def self.drop_database_previous_password_check_functions(db, opts={})
|
121
|
-
drop_database_authentication_functions(db, {:get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match}.merge(opts))
|
132
|
+
drop_database_authentication_functions(db, {:table_name=>:account_previous_password_hashes, :get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match}.merge(opts))
|
122
133
|
end
|
123
134
|
end
|
data/lib/rodauth/version.rb
CHANGED
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.7.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: 2020-
|
11
|
+
date: 2020-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -277,6 +277,7 @@ extra_rdoc_files:
|
|
277
277
|
- doc/webauthn_verify_account.rdoc
|
278
278
|
- doc/active_sessions.rdoc
|
279
279
|
- doc/audit_logging.rdoc
|
280
|
+
- doc/password_pepper.rdoc
|
280
281
|
- doc/release_notes/1.17.0.txt
|
281
282
|
- doc/release_notes/1.0.0.txt
|
282
283
|
- doc/release_notes/1.1.0.txt
|
@@ -304,6 +305,11 @@ extra_rdoc_files:
|
|
304
305
|
- doc/release_notes/2.0.0.txt
|
305
306
|
- doc/release_notes/2.1.0.txt
|
306
307
|
- doc/release_notes/2.2.0.txt
|
308
|
+
- doc/release_notes/2.3.0.txt
|
309
|
+
- doc/release_notes/2.4.0.txt
|
310
|
+
- doc/release_notes/2.5.0.txt
|
311
|
+
- doc/release_notes/2.6.0.txt
|
312
|
+
- doc/release_notes/2.7.0.txt
|
307
313
|
files:
|
308
314
|
- CHANGELOG
|
309
315
|
- MIT-LICENSE
|
@@ -356,6 +362,7 @@ files:
|
|
356
362
|
- doc/password_complexity.rdoc
|
357
363
|
- doc/password_expiration.rdoc
|
358
364
|
- doc/password_grace_period.rdoc
|
365
|
+
- doc/password_pepper.rdoc
|
359
366
|
- doc/recovery_codes.rdoc
|
360
367
|
- doc/release_notes/1.0.0.txt
|
361
368
|
- doc/release_notes/1.1.0.txt
|
@@ -384,6 +391,11 @@ files:
|
|
384
391
|
- doc/release_notes/2.0.0.txt
|
385
392
|
- doc/release_notes/2.1.0.txt
|
386
393
|
- doc/release_notes/2.2.0.txt
|
394
|
+
- doc/release_notes/2.3.0.txt
|
395
|
+
- doc/release_notes/2.4.0.txt
|
396
|
+
- doc/release_notes/2.5.0.txt
|
397
|
+
- doc/release_notes/2.6.0.txt
|
398
|
+
- doc/release_notes/2.7.0.txt
|
387
399
|
- doc/remember.rdoc
|
388
400
|
- doc/reset_password.rdoc
|
389
401
|
- doc/session_expiration.rdoc
|
@@ -427,6 +439,7 @@ files:
|
|
427
439
|
- lib/rodauth/features/password_complexity.rb
|
428
440
|
- lib/rodauth/features/password_expiration.rb
|
429
441
|
- lib/rodauth/features/password_grace_period.rb
|
442
|
+
- lib/rodauth/features/password_pepper.rb
|
430
443
|
- lib/rodauth/features/recovery_codes.rb
|
431
444
|
- lib/rodauth/features/remember.rb
|
432
445
|
- lib/rodauth/features/reset_password.rb
|
@@ -526,7 +539,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
526
539
|
- !ruby/object:Gem::Version
|
527
540
|
version: '0'
|
528
541
|
requirements: []
|
529
|
-
rubygems_version: 3.1.
|
542
|
+
rubygems_version: 3.1.4
|
530
543
|
signing_key:
|
531
544
|
specification_version: 4
|
532
545
|
summary: Authentication and Account Management Framework for Rack Applications
|