rodauth 1.15.0 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ = Documentation for Disallow Common Passwords Feature
2
+
3
+ The disallow common passwords feature disallows setting of a password
4
+ that matches one of the most common passwords. By default, a list of
5
+ 10,000 of the most common passwords is used, but you can supply your
6
+ own file. Using a larger list is recommended, but Rodauth doesn't
7
+ ship with a larger list to avoid bloating the size of the gem.
8
+
9
+ == Auth Value Methods
10
+
11
+ most_common_passwords :: An object that responds to +include?+ which will return true if the password given is one of the most common passwords. Useful for custom password sets where they are not stored in files and kept in memory.
12
+ most_common_passwords_file :: The path to the file containing the most common passwords, which are not allowed to be used for new passwords. Defaults to a list of 10,000 most common passwords that ships with Rodauth. Can be set to nil/false if you do not want to to load common passwords from a file.
13
+ password_is_one_of_the_most_common_message :: The error message fragment to display if the given password matches one of the most common passwords.
14
+
15
+ == Auth Methods
16
+
17
+ password_one_of_most_common?(password) :: This can be used to override the default check for whether the given password is contained in the most_common_passwords_file. This method may be useful when using very large password databases where you don't want to keep the list of most common passwords in memory.
@@ -0,0 +1,31 @@
1
+ = New Features
2
+
3
+ * A disallow_common_passwords feature has been added. This feature
4
+ by default will disallow the 10,000 most common passwords:
5
+
6
+ enable :disallow_common_passwords
7
+
8
+ You can supply your own file containing common passwords separated
9
+ by newlines ("\n"):
10
+
11
+ most_common_passwords_file '/path/to/file'
12
+
13
+ You can also supply a password dictionary directly as any object
14
+ that responds to include?:
15
+
16
+ most_common_passwords some_password_dictionary_object
17
+
18
+ The reason only the 10,000 most common passwords are used by
19
+ default is larger password files would significantly bloat the
20
+ size of the gem. Also, because the most common passwords are kept
21
+ in memory by default for performance reasons, larger password
22
+ files can bloat the memory usage of the process (the
23
+ disallow_common_passwords feature should use around 500KB of
24
+ memory by default). For very large password dictionaries,
25
+ consider using a custom object that does not keep all common
26
+ passwords in memory.
27
+
28
+ = Other Improvements
29
+
30
+ * Rodauth no longer uses the Rack::Request#[] method to get
31
+ parameter values. This method is deprecated in Rack 2.
@@ -6,6 +6,19 @@ password confirmation later for such sessions if they are accessing a
6
6
  section requiring more security. The remember feature depends on the
7
7
  logout feature.
8
8
 
9
+ By default, the remember feature just supports a form that the user can use
10
+ to change their remember settings for the current browser. They can either
11
+ enable remembering for the browser, forget it for the browser, or disable
12
+ it completely so that any remembering for other browsers is removed as well.
13
+
14
+ In some cases, you may want to force remembering on users if you don't want
15
+ to force them to turn it on manually. For example, if you want to force
16
+ remembering on login, you can do that via:
17
+
18
+ after_login do
19
+ remember_login
20
+ end
21
+
9
22
  == Auth Value Methods
10
23
 
11
24
  extend_remember_deadline? :: Whether to extend the remember token deadline
@@ -46,7 +59,6 @@ remember_param :: The parameter name to use for the remember password settings
46
59
  remembered_session_key :: The key in the session storing whether the current
47
60
  session has been autologged in via remember token.
48
61
 
49
-
50
62
  == Auth Methods
51
63
 
52
64
  add_remember_key :: Add a remember key for the current account to the remember
@@ -286,8 +286,6 @@ module Rodauth
286
286
  session[session_key] = account_session_value
287
287
  end
288
288
 
289
- private
290
-
291
289
  # Return a string for the parameter name. This will be an empty
292
290
  # string if the parameter doesn't exist.
293
291
  def param(key)
@@ -301,6 +299,8 @@ module Rodauth
301
299
  value.to_s unless value.nil?
302
300
  end
303
301
 
302
+ private
303
+
304
304
  def redirect(path)
305
305
  request.redirect(path)
306
306
  end
@@ -0,0 +1,39 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:disallow_common_passwords, :DisallowCommonPasswords) do
5
+ depends :login_password_requirements_base
6
+
7
+ auth_value_method :most_common_passwords_file, File.join(File.dirname(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__))))), 'dict', 'top-10_000-passwords.txt')
8
+ auth_value_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
9
+ auth_value_method :most_common_passwords, nil
10
+
11
+ auth_methods :password_one_of_most_common?
12
+
13
+ def password_meets_requirements?(password)
14
+ super && password_not_one_of_the_most_common?(password)
15
+ end
16
+
17
+ def post_configure
18
+ super
19
+
20
+ return if most_common_passwords || !most_common_passwords_file
21
+
22
+ require 'set'
23
+ most_common = Set.new(File.read(most_common_passwords_file).split("\n").each(&:freeze)).freeze
24
+ self.class.send(:define_method, :most_common_passwords){most_common}
25
+ end
26
+
27
+ def password_one_of_most_common?(password)
28
+ most_common_passwords.include?(password)
29
+ end
30
+
31
+ private
32
+
33
+ def password_not_one_of_the_most_common?(password)
34
+ return true unless password_one_of_most_common?(password)
35
+ @password_requirement_message = password_is_one_of_the_most_common_message
36
+ false
37
+ end
38
+ end
39
+ end
@@ -69,7 +69,7 @@ module Rodauth
69
69
 
70
70
  def after_create_account
71
71
  if account_password_hash_column
72
- add_previous_password_hash(password_hash(request[password_param]))
72
+ add_previous_password_hash(password_hash(param(password_param)))
73
73
  end
74
74
  super if defined?(super)
75
75
  end
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VERSION = '1.15.0'.freeze
4
+ VERSION = '1.16.0'.freeze
5
5
 
6
6
  def self.version
7
7
  VERSION
@@ -0,0 +1,93 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth disallow common passwords feature' do
4
+ it "should check that password used is not one of the most common" do
5
+ rodauth do
6
+ enable :login, :change_password, :disallow_common_passwords
7
+ change_password_requires_password? false
8
+ password_minimum_length 1
9
+ end
10
+ roda do |r|
11
+ r.rodauth
12
+ r.root{view :content=>""}
13
+ end
14
+
15
+ login
16
+ page.current_path.must_equal '/'
17
+
18
+ visit '/change-password'
19
+
20
+ bad_password_file = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'dict', 'top-10_000-passwords.txt')
21
+ File.read(bad_password_file).split.shuffle.take(5).each do |pass|
22
+ fill_in 'New Password', :with=>pass
23
+ fill_in 'Confirm Password', :with=>pass
24
+ click_button 'Change Password'
25
+ page.html.must_include("invalid password, does not meet requirements (is one of the most common passwords)")
26
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
27
+ end
28
+
29
+ fill_in 'New Password', :with=>'footpassword'
30
+ fill_in 'Confirm Password', :with=>'footpassword'
31
+ click_button 'Change Password'
32
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
33
+ end
34
+
35
+ it "should check that password used is not one of the most common with custom password set" do
36
+ rodauth do
37
+ enable :login, :change_password, :disallow_common_passwords
38
+ change_password_requires_password? false
39
+ most_common_passwords ['foobarbaz']
40
+ end
41
+ roda do |r|
42
+ r.rodauth
43
+ r.root{view :content=>""}
44
+ end
45
+
46
+ login
47
+ page.current_path.must_equal '/'
48
+
49
+ visit '/change-password'
50
+
51
+ fill_in 'New Password', :with=>'foobarbaz'
52
+ fill_in 'Confirm Password', :with=>'foobarbaz'
53
+ click_button 'Change Password'
54
+ page.html.must_include("invalid password, does not meet requirements (is one of the most common passwords)")
55
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
56
+
57
+ fill_in 'New Password', :with=>'footpassword'
58
+ fill_in 'Confirm Password', :with=>'footpassword'
59
+ click_button 'Change Password'
60
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
61
+ end
62
+
63
+ it "should check that password used is not one of the most common with custom check" do
64
+ rodauth do
65
+ enable :login, :change_password, :disallow_common_passwords
66
+ change_password_requires_password? false
67
+ most_common_passwords_file nil
68
+ password_one_of_most_common? do |password|
69
+ password == 'foobarbaz'
70
+ end
71
+ end
72
+ roda do |r|
73
+ r.rodauth
74
+ r.root{view :content=>""}
75
+ end
76
+
77
+ login
78
+ page.current_path.must_equal '/'
79
+
80
+ visit '/change-password'
81
+
82
+ fill_in 'New Password', :with=>'foobarbaz'
83
+ fill_in 'Confirm Password', :with=>'foobarbaz'
84
+ click_button 'Change Password'
85
+ page.html.must_include("invalid password, does not meet requirements (is one of the most common passwords)")
86
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
87
+
88
+ fill_in 'New Password', :with=>'footpassword'
89
+ fill_in 'Confirm Password', :with=>'footpassword'
90
+ click_button 'Change Password'
91
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
92
+ end
93
+ end
@@ -57,7 +57,7 @@ describe 'Rodauth login feature' do
57
57
  end
58
58
  roda do |r|
59
59
  r.post 'login' do
60
- if r['login'] == 'apple' && r['password'] == 'banana'
60
+ if r.params['login'] == 'apple' && r.params['password'] == 'banana'
61
61
  session[:user_id] = 'pear'
62
62
  r.redirect '/'
63
63
  end
@@ -119,7 +119,7 @@ describe 'Rodauth login feature' do
119
119
  session_key :login_email
120
120
  account_from_session{DB[:accounts].first(:email=>session_value)}
121
121
  account_session_value{account[:email]}
122
- login_param{request['lp']}
122
+ login_param{param('lp')}
123
123
  login_additional_form_tags "<input type='hidden' name='lp' value='l' />"
124
124
  password_param 'p'
125
125
  login_redirect{"/foo/#{account[:email]}"}
@@ -103,6 +103,10 @@ class Minitest::HooksSpec
103
103
  jwt = type == :jwt || type == :jwt_html
104
104
 
105
105
  app = Class.new(jwt_only ? JsonBase : Base)
106
+ begin
107
+ app.plugin :request_aref, :raise
108
+ rescue LoadError
109
+ end
106
110
  app.opts[:unsupported_block_result] = :raise
107
111
  app.opts[:unsupported_matcher] = :raise
108
112
  app.opts[:verbatim_string_matcher] = true
@@ -1,6 +1,6 @@
1
1
  <form method="post" class="form-horizontal" role="form" id="login-form">
2
2
  #{csrf_tag if respond_to?(:csrf_tag)}
3
- <input type="hidden" name="lp" value="#{request['lp']}"/>
3
+ <input type="hidden" name="lp" value="#{rodauth.param('lp')}"/>
4
4
  <div class="form-group">
5
5
  <label class="col-sm-2 control-label" for="login">Login</label>
6
6
  <div class="col-sm-10">
@@ -1,6 +1,6 @@
1
1
  <div class="form-group">
2
2
  <label class="col-sm-2 control-label" for="login-confirm">#{rodauth.login_confirm_label}</label>
3
3
  <div class="col-sm-10">
4
- <input type="text" class="form-control" name="login-confirm" id="#{rodauth.login_confirm_param}" value="#{h request[rodauth.login_confirm_param]}"/>
4
+ <input type="text" class="form-control" name="login-confirm" id="#{rodauth.login_confirm_param}" value="#{h rodauth.param(rodauth.login_confirm_param)}"/>
5
5
  </div>
6
6
  </div>
@@ -1,6 +1,6 @@
1
1
  <div class="form-group">
2
2
  <label class="col-sm-2 control-label" for="login">#{rodauth.login_label}</label>
3
3
  <div class="col-sm-10">
4
- <input type="text" class="form-control#{' error' if rodauth.field_error(rodauth.login_param)}" name="#{rodauth.login_param}" id="login" value="#{h request[rodauth.login_param]}"/> #{rodauth.field_error(rodauth.login_param)}
4
+ <input type="text" class="form-control#{' error' if rodauth.field_error(rodauth.login_param)}" name="#{rodauth.login_param}" id="login" value="#{h rodauth.param(rodauth.login_param)}"/> #{rodauth.field_error(rodauth.login_param)}
5
5
  </div>
6
6
  </div>
@@ -2,6 +2,6 @@
2
2
  #{rodauth.reset_password_request_additional_form_tags}
3
3
  #{rodauth.csrf_tag}
4
4
  <p>If you have forgotten your password, you can request a password reset: </p>
5
- #{(login = request[rodauth.login_param]) ? "<input type=\"hidden\" name=\"#{rodauth.login_param}\" value=\"#{h login}\"/>" : rodauth.render('login-field')}
5
+ #{(login = rodauth.param_or_nil(rodauth.login_param)) ? "<input type=\"hidden\" name=\"#{rodauth.login_param}\" value=\"#{h login}\"/>" : rodauth.render('login-field')}
6
6
  #{rodauth.button(rodauth.reset_password_request_button)}
7
7
  </form>
@@ -5,7 +5,7 @@
5
5
  <div class="form-group">
6
6
  <label class="col-sm-2 control-label" for="sms-phone">#{rodauth.sms_phone_label}</label>
7
7
  <div class="col-sm-3">
8
- <input type="text" class="form-control#{' error' if rodauth.field_error(rodauth.sms_phone_param)}" name="#{rodauth.sms_phone_param}" id="sms-phone" value="#{h request[rodauth.sms_phone_param]}"/> #{rodauth.field_error(rodauth.sms_phone_param)}
8
+ <input type="text" class="form-control#{' error' if rodauth.field_error(rodauth.sms_phone_param)}" name="#{rodauth.sms_phone_param}" id="sms-phone" value="#{h rodauth.param(rodauth.sms_phone_param)}"/> #{rodauth.field_error(rodauth.sms_phone_param)}
9
9
  </div>
10
10
  </div>
11
11
  #{rodauth.button(rodauth.sms_setup_button)}
@@ -1,7 +1,7 @@
1
1
  <form action="#{rodauth.prefix}/#{rodauth.unlock_account_request_route}" method="post" class="rodauth form-horizontal" role="form" id="unlock-account-request-form">
2
2
  #{rodauth.unlock_account_request_additional_form_tags}
3
3
  #{rodauth.csrf_tag}
4
- <input type="hidden" name="#{rodauth.login_param}" value="#{h request[rodauth.login_param]}"/>
4
+ <input type="hidden" name="#{rodauth.login_param}" value="#{h rodauth.param(rodauth.login_param)}"/>
5
5
  This account is currently locked out. You can request that the account be unlocked:
6
6
  <input type="submit" class="btn btn-primary inline" value="#{rodauth.unlock_account_request_button}"/>
7
7
  </form>
@@ -2,6 +2,6 @@
2
2
  #{rodauth.verify_account_resend_additional_form_tags}
3
3
  #{rodauth.csrf_tag}
4
4
  <p>If you no longer have the email to verify the account, you can request that it be resent to you:</p>
5
- #{(login = request[rodauth.login_param]) ? "<input type=\"hidden\" name=\"#{rodauth.login_param}\" value=\"#{h login}\"/>" : rodauth.render('login-field')}
5
+ #{(login = rodauth.param_or_nil(rodauth.login_param)) ? "<input type=\"hidden\" name=\"#{rodauth.login_param}\" value=\"#{h login}\"/>" : rodauth.render('login-field')}
6
6
  #{rodauth.button(rodauth.verify_account_resend_button)}
7
7
  </form>
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: 1.15.0
4
+ version: 1.16.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: 2018-01-29 00:00:00.000000000 Z
11
+ date: 2018-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -229,6 +229,7 @@ extra_rdoc_files:
229
229
  - doc/verify_login_change.rdoc
230
230
  - doc/internals.rdoc
231
231
  - doc/change_password_notify.rdoc
232
+ - doc/disallow_common_passwords.rdoc
232
233
  - doc/release_notes/1.0.0.txt
233
234
  - doc/release_notes/1.1.0.txt
234
235
  - doc/release_notes/1.2.0.txt
@@ -245,11 +246,13 @@ extra_rdoc_files:
245
246
  - doc/release_notes/1.13.0.txt
246
247
  - doc/release_notes/1.14.0.txt
247
248
  - doc/release_notes/1.15.0.txt
249
+ - doc/release_notes/1.16.0.txt
248
250
  files:
249
251
  - CHANGELOG
250
252
  - MIT-LICENSE
251
253
  - README.rdoc
252
254
  - Rakefile
255
+ - dict/top-10_000-passwords.txt
253
256
  - doc/account_expiration.rdoc
254
257
  - doc/base.rdoc
255
258
  - doc/change_login.rdoc
@@ -258,6 +261,7 @@ files:
258
261
  - doc/close_account.rdoc
259
262
  - doc/confirm_password.rdoc
260
263
  - doc/create_account.rdoc
264
+ - doc/disallow_common_passwords.rdoc
261
265
  - doc/disallow_password_reuse.rdoc
262
266
  - doc/email_base.rdoc
263
267
  - doc/http_basic_auth.rdoc
@@ -280,6 +284,7 @@ files:
280
284
  - doc/release_notes/1.13.0.txt
281
285
  - doc/release_notes/1.14.0.txt
282
286
  - doc/release_notes/1.15.0.txt
287
+ - doc/release_notes/1.16.0.txt
283
288
  - doc/release_notes/1.2.0.txt
284
289
  - doc/release_notes/1.3.0.txt
285
290
  - doc/release_notes/1.4.0.txt
@@ -309,6 +314,7 @@ files:
309
314
  - lib/rodauth/features/close_account.rb
310
315
  - lib/rodauth/features/confirm_password.rb
311
316
  - lib/rodauth/features/create_account.rb
317
+ - lib/rodauth/features/disallow_common_passwords.rb
312
318
  - lib/rodauth/features/disallow_password_reuse.rb
313
319
  - lib/rodauth/features/email_base.rb
314
320
  - lib/rodauth/features/http_basic_auth.rb
@@ -343,6 +349,7 @@ files:
343
349
  - spec/close_account_spec.rb
344
350
  - spec/confirm_password_spec.rb
345
351
  - spec/create_account_spec.rb
352
+ - spec/disallow_common_passwords_spec.rb
346
353
  - spec/disallow_password_reuse_spec.rb
347
354
  - spec/http_basic_auth_spec.rb
348
355
  - spec/jwt_spec.rb