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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +50 -0
  3. data/README.rdoc +14 -0
  4. data/doc/base.rdoc +3 -1
  5. data/doc/jwt_refresh.rdoc +13 -0
  6. data/doc/login.rdoc +8 -0
  7. data/doc/login_password_requirements_base.rdoc +3 -0
  8. data/doc/password_pepper.rdoc +44 -0
  9. data/doc/recovery_codes.rdoc +2 -1
  10. data/doc/release_notes/2.3.0.txt +37 -0
  11. data/doc/release_notes/2.4.0.txt +22 -0
  12. data/doc/release_notes/2.5.0.txt +20 -0
  13. data/doc/release_notes/2.6.0.txt +37 -0
  14. data/doc/release_notes/2.7.0.txt +33 -0
  15. data/doc/remember.rdoc +1 -1
  16. data/doc/verify_login_change.rdoc +1 -0
  17. data/javascript/webauthn_auth.js +9 -9
  18. data/javascript/webauthn_setup.js +9 -6
  19. data/lib/rodauth.rb +14 -6
  20. data/lib/rodauth/features/base.rb +19 -4
  21. data/lib/rodauth/features/change_password.rb +1 -1
  22. data/lib/rodauth/features/close_account.rb +8 -6
  23. data/lib/rodauth/features/confirm_password.rb +2 -2
  24. data/lib/rodauth/features/disallow_password_reuse.rb +4 -2
  25. data/lib/rodauth/features/email_auth.rb +1 -1
  26. data/lib/rodauth/features/jwt.rb +11 -3
  27. data/lib/rodauth/features/jwt_refresh.rb +70 -8
  28. data/lib/rodauth/features/login.rb +23 -12
  29. data/lib/rodauth/features/login_password_requirements_base.rb +9 -4
  30. data/lib/rodauth/features/otp.rb +0 -2
  31. data/lib/rodauth/features/password_pepper.rb +45 -0
  32. data/lib/rodauth/features/recovery_codes.rb +22 -1
  33. data/lib/rodauth/features/remember.rb +6 -1
  34. data/lib/rodauth/features/session_expiration.rb +1 -6
  35. data/lib/rodauth/features/verify_account.rb +6 -7
  36. data/lib/rodauth/features/verify_login_change.rb +2 -1
  37. data/lib/rodauth/features/webauthn_login.rb +1 -1
  38. data/lib/rodauth/migrations.rb +16 -5
  39. data/lib/rodauth/version.rb +1 -1
  40. 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 =~ /\A[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+\z/
108
- return true
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
@@ -76,9 +76,7 @@ module Rodauth
76
76
  )
77
77
 
78
78
  auth_methods(
79
- :otp,
80
79
  :otp_exists?,
81
- :otp_key,
82
80
  :otp_last_use,
83
81
  :otp_locked_out?,
84
82
  :otp_new_secret,
@@ -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
- ::Rack::Utils.delete_cookie_header!(response.headers, remember_cookie_key, remember_cookie_options)
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
- "An email has been sent to you with a link to verify your login change"
135
+ change_login_needs_verification_notice_flash
135
136
  end
136
137
 
137
138
  def verify_login_change_old_login
@@ -22,7 +22,7 @@ module Rodauth
22
22
 
23
23
  webauthn_credential = webauthn_auth_credential_from_form_submission
24
24
  before_webauthn_login
25
- _login('webauthn') do
25
+ login('webauthn') do
26
26
  webauthn_update_session(webauthn_credential.id)
27
27
  end
28
28
  end
@@ -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 int8) RETURNS text AS $$
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 int8, hash text) RETURNS boolean AS $$
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
- db.run "DROP FUNCTION #{get_salt_name}(int8)"
109
- db.run "DROP FUNCTION #{valid_hash_name}(int8, text)"
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
@@ -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 = 2
9
+ MINOR = 7
10
10
 
11
11
  # The patch version of Rodauth, updated only for bug fixes from the last
12
12
  # feature release.
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.2.0
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-07-20 00:00:00.000000000 Z
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.2
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