rodauth 1.15.0 → 1.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +6 -0
- data/README.rdoc +8 -19
- data/dict/top-10_000-passwords.txt +10000 -0
- data/doc/disallow_common_passwords.rdoc +17 -0
- data/doc/release_notes/1.16.0.txt +31 -0
- data/doc/remember.rdoc +13 -1
- data/lib/rodauth/features/base.rb +2 -2
- data/lib/rodauth/features/disallow_common_passwords.rb +39 -0
- data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
- data/lib/rodauth/version.rb +1 -1
- data/spec/disallow_common_passwords_spec.rb +93 -0
- data/spec/login_spec.rb +2 -2
- data/spec/spec_helper.rb +4 -0
- data/spec/views/login.str +1 -1
- data/templates/login-confirm-field.str +1 -1
- data/templates/login-field.str +1 -1
- data/templates/reset-password-request.str +1 -1
- data/templates/sms-setup.str +1 -1
- data/templates/unlock-account-request.str +1 -1
- data/templates/verify-account-resend.str +1 -1
- metadata +9 -2
@@ -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.
|
data/doc/remember.rdoc
CHANGED
@@ -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(
|
72
|
+
add_previous_password_hash(password_hash(param(password_param)))
|
73
73
|
end
|
74
74
|
super if defined?(super)
|
75
75
|
end
|
data/lib/rodauth/version.rb
CHANGED
@@ -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
|
data/spec/login_spec.rb
CHANGED
@@ -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{
|
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]}"}
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
data/spec/views/login.str
CHANGED
@@ -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="#{
|
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
|
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>
|
data/templates/login-field.str
CHANGED
@@ -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
|
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 =
|
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>
|
data/templates/sms-setup.str
CHANGED
@@ -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
|
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
|
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 =
|
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.
|
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-
|
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
|