rodauth 2.2.0 → 2.7.0

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