auth: Make min password length and strength configurable.

This adds some configuration options to settings.py, namely
PASSWORD_MIN_LENGTH and PASSWORD_MIN_QUALITY, which control
when the frontend validator invalidates the password.

Closes #2628
This commit is contained in:
Bojidar Marinov
2017-01-09 19:04:23 +02:00
committed by showell
parent e6c3aaae12
commit 786dd0fca4
10 changed files with 48 additions and 23 deletions

View File

@@ -391,9 +391,8 @@ announcement).
the standard PBKDF2 algorithm. Password strength is checked and
weak passwords are visually discouraged using the `zxcvbn` library,
but Zulip does not by default have strong requirements on user
password strength. Modify `static/js/common.js` to adjust the
password strength requirements (patches welcome to make this
controllable by an easy setting!).
password strength. Modify the settings to adjust the password
strength requirements (length and `zxcvbn` minimum quality).
* Zulip requires CSRF tokens in all interactions with the web API to
prevent CSRF attacks.

View File

@@ -63,7 +63,7 @@ casper.then(function () {
full_name: 'Alice',
realm_name: organization_name,
realm_subdomain: subdomain,
password: 'password',
password: 'passwordwhichisreallyreallyreallycomplexandnotguessable',
terms: true,
}, true);
});

View File

@@ -14,27 +14,39 @@ function autofocus(selector) {
//
// This is in common.js because we want to use it from the signup page
// and also from the in-app password change interface.
function password_quality(password, bar) {
function password_quality(password, bar, password_field) {
// We load zxcvbn.js asynchronously, so the variable might not be set.
if (typeof zxcvbn === 'undefined') {
return undefined;
}
var min_length = 6;
var min_quality = 0;
if (password_field) {
min_length = password_field.data('minLength') || min_length;
min_quality = password_field.data('minQuality') || min_quality;
}
// Consider the password acceptable if it's at least 6 characters.
var acceptable = password.length >= 6;
var acceptable = password.length >= min_length;
// Compute a quality score in [0,1].
var result = zxcvbn(password);
var quality = Math.min(1,Math.log(1 + result.crack_times_seconds.
offline_slow_hashing_1e4_per_second) / 22);
// Even if zxcvbn loves your short password, the bar should be filled
// at most 1/3 of the way, because we won't accept it.
if (!acceptable) {
quality = Math.min(quality, 0.33);
// In case the quality is below the minimum, we should not accept the password
} else if (quality < min_quality) {
acceptable = false;
}
if (bar !== undefined) {
// Compute a quality score in [0,1].
var result = zxcvbn(password);
var quality = Math.min(1,Math.log(1 + result.crack_times_seconds.
offline_slow_hashing_1e4_per_second) / 22);
// Even if zxcvbn loves your short password, the bar should be filled
// at most 1/3 of the way, because we won't accept it.
if (!acceptable) {
quality = Math.min(quality, 0.33);
}
// Display the password quality score on a progress bar
// which bottoms out at 10% so there's always something
// for the user to see.

View File

@@ -3,7 +3,7 @@ $(function () {
// some of the jQuery selectors below will return empty lists.
$.validator.addMethod('password_strength', function (value) {
return password_quality(value);
return password_quality(value, undefined, $('#id_password, #id_new_password1'));
}, 'Password is weak.');
function highlight(class_to_add) {
@@ -33,7 +33,7 @@ $(function () {
$('#id_password, #id_new_password1').on('change keyup', function () {
// Update the password strength bar even if we aren't validating
// the field yet.
password_quality($(this).val(), $('#pw_strength .bar'));
password_quality($(this).val(), $('#pw_strength .bar'), $(this));
});
$("#send_confirm").validate({

View File

@@ -211,7 +211,8 @@ function _setup_page() {
});
$('#new_password').on('change keyup', function () {
password_quality($('#new_password').val(), $('#pw_strength .bar'));
var field = $('#new_password');
password_quality(field.val(), $('#pw_strength .bar'), field);
});
if (!page_params.show_digest_email) {
@@ -241,9 +242,10 @@ function _setup_page() {
if (page_params.password_auth_enabled !== false) {
// FIXME: Check that the two password fields match
// FIXME: Use the same jQuery validation plugin as the signup form?
var field = $('#new_password');
var new_pw = $('#new_password').val();
if (new_pw !== '') {
var password_ok = password_quality(new_pw);
var password_ok = password_quality(new_pw, undefined, field);
if (password_ok === undefined) {
// zxcvbn.js didn't load, for whatever reason.
settings_change_error(

View File

@@ -53,7 +53,9 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
<label for="id_password" class="inline-block">{{ _('Password') }}</label>
<input id="id_password" class="required" type="password" name="password"
value="{% if form.password.value() %}{{ form.password.value() }}{% endif %}"
maxlength="100" />
maxlength="100"
data-min-length="{{password_min_length}}"
data-min-quality="{{password_min_quality}}"/>
{% if full_name %}
<span class="help-inline">
{{ _('This is used for mobile applications and other tools that require a password.') }}

View File

@@ -28,7 +28,9 @@
<div class="">
<input id="id_new_password1" class="required" type="password" name="new_password1"
value="{% if form.new_password1.value() %}{{ form.new_password1.value() }}{% endif %}"
maxlength="100" />
maxlength="100"
data-min-length="{{password_min_length}}"
data-min-quality="{{password_min_quality}}" />
{% if form.new_password1.errors %}
{% for error in form.new_password1.errors %}
<div class="alert alert-error">{{ error }}</div>

View File

@@ -61,6 +61,8 @@ def add_settings(request):
'development_environment': settings.DEVELOPMENT,
'support_email': settings.ZULIP_ADMINISTRATOR,
'find_team_link_disabled': settings.FIND_TEAM_LINK_DISABLED,
'password_min_length': settings.PASSWORD_MIN_LENGTH,
'password_min_quality': settings.PASSWORD_MIN_ZXCVBN_QUALITY,
}

View File

@@ -121,6 +121,10 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# Session cookie expiry in seconds after the last page load
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # 2 weeks
# Controls password strength requirements
PASSWORD_MIN_LENGTH = 6
PASSWORD_MIN_ZXCVBN_QUALITY = 0.4 # 0 to disable
# Controls whether or not there is a feedback button in the UI.
ENABLE_FEEDBACK = False

View File

@@ -191,6 +191,8 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
'POST_MIGRATION_CACHE_FLUSHING': False,
'ENABLE_FILE_LINKS': False,
'USE_WEBSOCKETS': True,
'PASSWORD_MIN_LENGTH': 6,
'PASSWORD_MIN_ZXCVBN_QUALITY': 0.4,
}
for setting_name, setting_val in six.iteritems(DEFAULT_SETTINGS):