Skip to content
This repository has been archived by the owner on Feb 9, 2020. It is now read-only.

Prevent long password denial of service #28

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions app/assets/javascripts/password_strength.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
var MULTIPLE_SYMBOLS_RE = /[!@#$%^&*?_~].*?[!@#$%^&*?_~]/;
var UPPERCASE_LOWERCASE_RE = /([a-z].*[A-Z])|([A-Z].*[a-z])/;
var SYMBOL_RE = /[!@#\$%^&*?_~]/;
var PASSWORD_LIMIT = 1000;
var USERNAME_LIMIT = 50000;

function escapeForRegexp(string) {
return (string || "").replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
Expand All @@ -20,6 +22,10 @@
PasswordStrength.fn.test = function() {
var score;
this.score = score = 0;
if (this.username)
this.username = this.username.substr(0, USERNAME_LIMIT);
if (this.password)
this.password = this.password.substr(0, PASSWORD_LIMIT);

if (this.containInvalidMatches()) {
this.status = "invalid";
Expand Down
6 changes: 4 additions & 2 deletions lib/password_strength/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ class Base
MULTIPLE_SYMBOLS_RE = /[!@#\$%^&*?_~-].*?[!@#\$%^&*?_~-]/
SYMBOL_RE = /[!@#\$%^&*?_~-]/
UPPERCASE_LOWERCASE_RE = /([a-z].*[A-Z])|([A-Z].*[a-z])/
PASSWORD_LIMIT = 1_000
USERNAME_LIMIT = 50_000
INVALID = :invalid
WEAK = :weak
STRONG = :strong
Expand Down Expand Up @@ -61,8 +63,8 @@ def self.common_words
end

def initialize(username, password, options = {})
@username = username.to_s
@password = password.to_s
@username = username.to_s[0...USERNAME_LIMIT]
@password = password.to_s[0...PASSWORD_LIMIT]
@score = 0
@exclude = options[:exclude]
@record = options[:record]
Expand Down
2 changes: 1 addition & 1 deletion lib/password_strength/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module PasswordStrength
module Version # :nodoc: all
MAJOR = 1
MINOR = 1
PATCH = 4
PATCH = 5
STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
end
end
16 changes: 14 additions & 2 deletions test/password_strength_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,20 @@ QUnit.test("test strong password", function(assert) {
assert.equal(strength.status, "strong");
});

QUnit.test("test truncate long password", function(assert) {
strength.password = "a".repeat(5000);
assert.equal(strength.password.length, 5000);
strength.test();
assert.equal(strength.password.length, 1000);
});

QUnit.test("test truncate long username", function(assert) {
strength.username = "a".repeat(100000);
assert.equal(strength.username.length, 100000);
strength.test();
assert.equal(strength.username.length, 50000);
});

QUnit.test("test weak password", function(assert) {
strength.password = "ytrewq";
strength.test()
Expand Down Expand Up @@ -251,8 +265,6 @@ QUnit.test("reject long passwords using same character", function(assert) {
strength.password = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
strength.test();
assert.equal(strength.status, "invalid");
// assert @strength.invalid?
// refute @strength.valid?
});

QUnit.module("PasswordStrength: jQuery integration", {
Expand Down
34 changes: 32 additions & 2 deletions test/password_strength_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
class TestPasswordStrength < Minitest::Test
def setup
@strength = PasswordStrength::Base.new("johndoe", "mypass")
@password_limit = PasswordStrength::Base.const_get(:PASSWORD_LIMIT)
@username_limit = PasswordStrength::Base.const_get(:USERNAME_LIMIT)
end

def teardown
set_const(:PASSWORD_LIMIT, @password_limit)
set_const(:USERNAME_LIMIT, @username_limit)
end

def test_shortcut
Expand Down Expand Up @@ -256,12 +263,35 @@ def test_loads_common_words
end

def test_reject_common_words
$BREAKPOINT = true
password = PasswordStrength::Base.common_words.first
@strength = PasswordStrength.test("johndoe", password)
assert @strength.invalid?, "#{password} must be invalid"
refute @strength.valid?
assert_equal :invalid, @strength.status
$BREAKPOINT = false
end

def test_long_passwords_same_as_truncated
set_const(:PASSWORD_LIMIT, 20)
strength_20 = PasswordStrength.test("johndoe", "ab"*10)
strength_200 = PasswordStrength.test("johndoe", "ab"*100)
assert strength_20.score == strength_200.score
assert strength_20.password == strength_200.password
assert strength_20.username == strength_200.username
assert strength_20.status == strength_200.status
end

def test_long_usernames_same_as_truncatedd
set_const(:USERNAME_LIMIT, 20)
strength_20 = PasswordStrength.test("ab"*10, "^Str0ng P4ssw0rd$")
strength_200 = PasswordStrength.test("ab"*100, "^Str0ng P4ssw0rd$")
assert strength_20.score == strength_200.score
assert strength_20.password == strength_200.password
assert strength_20.username == strength_200.username
assert strength_20.status == strength_200.status
end

def set_const(const, value)
PasswordStrength::Base.send(:remove_const, const)
PasswordStrength::Base.const_set(const, value)
end
end