mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	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:
		@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ casper.then(function () {
 | 
			
		||||
            full_name: 'Alice',
 | 
			
		||||
            realm_name: organization_name,
 | 
			
		||||
            realm_subdomain: subdomain,
 | 
			
		||||
            password: 'password',
 | 
			
		||||
            password: 'passwordwhichisreallyreallyreallycomplexandnotguessable',
 | 
			
		||||
            terms: true,
 | 
			
		||||
        }, true);
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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({
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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.') }}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user