Merge 84fb6b10bd
into 0b2ad520b7
This commit is contained in:
commit
f15fdd3540
|
@ -189,6 +189,15 @@ class Setting(db.Model):
|
|||
'ttl_options': '1 minute,5 minutes,30 minutes,60 minutes,24 hours',
|
||||
'otp_field_enabled': True,
|
||||
'custom_css': '',
|
||||
'pwd_min_len' : 10,
|
||||
'pwd_min_lowercase' : 3,
|
||||
'pwd_min_uppercase' : 2,
|
||||
'pwd_min_digits' : 2,
|
||||
'pwd_min_special' : 1,
|
||||
'pwd_must_not_contain' : 'username,firstname',
|
||||
'max_history_records': 1000,
|
||||
'zxcvbn_enabled': False,
|
||||
'zxcvbn_guesses_log' : 11,
|
||||
'otp_force': False,
|
||||
'max_history_records': 1000
|
||||
}
|
||||
|
|
|
@ -1397,7 +1397,17 @@ def setting_authentication():
|
|||
local_db_enabled = True if request.form.get(
|
||||
'local_db_enabled') else False
|
||||
signup_enabled = True if request.form.get(
|
||||
'signup_enabled', ) else False
|
||||
'signup_enabled') else False
|
||||
enable_zxcvbn = request.form.get('pass-policy-sel') == 'heuristic-policy'
|
||||
if not enable_zxcvbn and signup_enabled:
|
||||
min_len = int(request.form.get('min_len'))
|
||||
min_lowercase = int(request.form.get('min_lowercase'))
|
||||
min_uppercase = int(request.form.get('min_uppercase'))
|
||||
min_digits = int(request.form.get('min_digits'))
|
||||
min_special = int(request.form.get('min_special'))
|
||||
must_not_contain = request.form.get('must_not_contain')
|
||||
elif enable_zxcvbn and signup_enabled:
|
||||
complexity = int(request.form.get('complexity'))
|
||||
|
||||
if not has_an_auth_method(local_db_enabled=local_db_enabled):
|
||||
result = {
|
||||
|
@ -1409,6 +1419,22 @@ def setting_authentication():
|
|||
else:
|
||||
Setting().set('local_db_enabled', local_db_enabled)
|
||||
Setting().set('signup_enabled', signup_enabled)
|
||||
if not enable_zxcvbn and signup_enabled:
|
||||
for attribute in must_not_contain.split(","):
|
||||
if attribute not in ['firstname','lastname','username','email']:
|
||||
result = {'status': False, 'msg': "Incorrect syntax in 'Must not contain' field"}
|
||||
return render_template('admin_setting_authentication.html', result=result)
|
||||
Setting().set('pwd_min_len', min_len)
|
||||
Setting().set('pwd_min_lowercase', min_lowercase)
|
||||
Setting().set('pwd_min_uppercase', min_uppercase)
|
||||
Setting().set('pwd_min_digits', min_digits)
|
||||
Setting().set('pwd_min_special', min_special)
|
||||
Setting().set('pwd_must_not_contain', must_not_contain)
|
||||
Setting().set('zxcvbn_enabled', False)
|
||||
elif enable_zxcvbn and signup_enabled:
|
||||
Setting().set('zxcvbn_guesses_log', complexity)
|
||||
Setting().set('zxcvbn_enabled', True)
|
||||
|
||||
result = {'status': True, 'msg': 'Saved successfully'}
|
||||
elif conf_type == 'ldap':
|
||||
ldap_enabled = True if request.form.get('ldap_enabled') else False
|
||||
|
|
|
@ -8,8 +8,9 @@ import base64
|
|||
from distutils.util import strtobool
|
||||
from yaml import Loader, load
|
||||
from onelogin.saml2.utils import OneLogin_Saml2_Utils
|
||||
from flask import Blueprint, render_template, make_response, url_for, current_app, g, session, request, redirect, abort
|
||||
from flask import Blueprint, render_template, render_template, make_response, url_for, current_app, g, session, request, redirect, abort, jsonify
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from zxcvbn import zxcvbn
|
||||
|
||||
from .base import login_manager
|
||||
from ..lib import utils
|
||||
|
@ -647,6 +648,107 @@ def logout():
|
|||
|
||||
return redirect(redirect_uri)
|
||||
|
||||
def password_quality_check(user, password):
|
||||
import string
|
||||
specialchars_vocab = "[!@#$%^&*()_+"
|
||||
def is_satisfied(setting_name, vocab):
|
||||
n_required_chars = int(Setting().get(setting_name))
|
||||
n_found_chars = 0
|
||||
for c in password:
|
||||
if c in vocab:
|
||||
n_found_chars += 1
|
||||
return n_found_chars >= n_required_chars
|
||||
|
||||
if len(password) < int(Setting().get('pwd_min_len')):
|
||||
return False
|
||||
if not is_satisfied('pwd_min_digits', string.digits) or \
|
||||
not is_satisfied('pwd_min_lowercase', string.ascii_lowercase) or \
|
||||
not is_satisfied('pwd_min_uppercase', string.ascii_uppercase) or \
|
||||
not is_satisfied('pwd_min_special', specialchars_vocab):
|
||||
return False
|
||||
must_not_contain_fields_str = Setting().get('pwd_must_not_contain')
|
||||
must_not_contain_fields = must_not_contain_fields_str.split(",")
|
||||
for contains in must_not_contain_fields:
|
||||
if "username" == contains:
|
||||
if re.search(user.username, password):
|
||||
return False
|
||||
elif "firstname" == contains:
|
||||
if re.search(user.firstname, password):
|
||||
return False
|
||||
elif "lastname" == contains:
|
||||
if re.search(user.lastname, password):
|
||||
return False
|
||||
elif "email" == contains:
|
||||
if re.search(user.email, password):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@index_bp.route('/ratepassword', methods=['POST'])
|
||||
def rate_password():
|
||||
# print("\n\nGot pass = ", passwd)
|
||||
# result = zxcvbn(pwd, user_inputs=[wordlist])
|
||||
logged_in = request.form['logged_in']
|
||||
if 'logged_in' in request.form and logged_in == 1:
|
||||
fname = current_user.firstname
|
||||
lname = current_user.lastname
|
||||
username = current_user.username
|
||||
email = current_user.email
|
||||
else:
|
||||
fname = request.form['fname']
|
||||
lname = request.form['lname']
|
||||
username = request.form['username']
|
||||
email = request.form['email']
|
||||
|
||||
password = request.form['password']
|
||||
inputs = []
|
||||
for i in [fname, lname, email, username]:
|
||||
if len(i) != 0:
|
||||
inputs.append(i)
|
||||
if len(password) == 0:
|
||||
return make_response(
|
||||
jsonify({
|
||||
'msg' : 'no-passwd',
|
||||
'feedback': '',
|
||||
'valid' : 'false',
|
||||
'strength': ''
|
||||
}), 200)
|
||||
|
||||
result = zxcvbn(password, user_inputs=inputs)
|
||||
defined_guesses_log = int(Setting().get('zxcvbn_guesses_log'))
|
||||
# attributes to return as json
|
||||
feedback = []
|
||||
rate = result['guesses_log10']/defined_guesses_log
|
||||
if rate < 0.5:
|
||||
strength = "very weak"
|
||||
if rate < 0.6:
|
||||
strength = "weak"
|
||||
elif rate < 1:
|
||||
strength = "medium"
|
||||
else:
|
||||
strength = "strong"
|
||||
|
||||
if result['guesses_log10'] < defined_guesses_log:
|
||||
feedback.append("Add more complexity to your password")
|
||||
for s in result['sequence']:
|
||||
if s['pattern'] == 'dictionary':
|
||||
if s['dictionary_name'] == 'user_inputs':
|
||||
feedback.append("Your password must not contain parts of your firstname, lastname, username or email")
|
||||
break
|
||||
for s in result['sequence']:
|
||||
if s['pattern'] == 'dictionary' and s['dictionary_name'] != 'user_inputs':
|
||||
feedback.append("Your password contains one or more words which exist in common wordlists.")
|
||||
break
|
||||
# in case complexity is high but feedback is still given, then downgrade to 'medium'
|
||||
if strength == "strong" and (len(feedback) != 0 or result['score'] < 4):
|
||||
strength = "medium"
|
||||
return make_response(
|
||||
jsonify({
|
||||
'feedback': feedback,
|
||||
'strength': strength,
|
||||
'valid' : 'true' if len(feedback) == 0 else 'false'
|
||||
}), 200)
|
||||
|
||||
|
||||
@index_bp.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
|
@ -676,6 +778,8 @@ def register():
|
|||
lastname=lastname,
|
||||
email=email)
|
||||
|
||||
if Setting().get('zxcvbn_enabled') == False and not password_quality_check(user, password):
|
||||
return render_template('register.html', error="Password does not meet the policy requirements")
|
||||
try:
|
||||
result = user.create_local_user()
|
||||
if result and result['status']:
|
||||
|
|
|
@ -4,6 +4,7 @@ from flask_login import current_user, login_required, login_manager
|
|||
|
||||
from ..models.user import User, Anonymous
|
||||
from ..models.setting import Setting
|
||||
from .index import password_quality_check
|
||||
|
||||
user_bp = Blueprint('user',
|
||||
__name__,
|
||||
|
@ -35,13 +36,15 @@ def before_request():
|
|||
@login_required
|
||||
def profile():
|
||||
if request.method == 'GET':
|
||||
return render_template('user_profile.html')
|
||||
return render_template('user_profile.html',user_info = current_user.get_user_info_by_username(), zxcvbn_enabled=Setting().get('zxcvbn_enabled') )
|
||||
if request.method == 'POST':
|
||||
if session['authentication_type'] == 'LOCAL':
|
||||
firstname = request.form.get('firstname', '').strip()
|
||||
lastname = request.form.get('lastname', '').strip()
|
||||
email = request.form.get('email', '').strip()
|
||||
new_password = request.form.get('password', '')
|
||||
if Setting().get('zxcvbn_enabled') == False and not password_quality_check(current_user, new_password):
|
||||
return render_template('user_profile.html', change_pass_tab = True, user_info = current_user.get_user_info_by_username() , zxcvbn_enabled=Setting().get('zxcvbn_enabled'), error="Password does not meet the policy requirements")
|
||||
else:
|
||||
firstname = lastname = email = new_password = ''
|
||||
current_app.logger.warning(
|
||||
|
@ -82,7 +85,7 @@ def profile():
|
|||
reload_info=False)
|
||||
user.update_profile()
|
||||
|
||||
return render_template('user_profile.html')
|
||||
return render_template('user_profile.html', user_info = current_user.get_user_info_by_username(), zxcvbn_enabled=Setting().get('zxcvbn_enabled'))
|
||||
|
||||
|
||||
@user_bp.route('/qrcode')
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td style="position: relative; top:10px;">
|
||||
<label>Minimum date:  </label>
|
||||
<label>Start date:  </label>
|
||||
</td>
|
||||
<td style="position: relative; top:10px;">
|
||||
<input type="text" id="min" name="min" class="datepicker" autocomplete="off" style=" border:1px solid #d2d6de; width:250px; height: 34px;">
|
||||
|
@ -135,7 +135,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td style="position: relative; top:20px;">
|
||||
<label>Maximum date:  </label>
|
||||
<label>End date:  </label>
|
||||
</td>
|
||||
<td style="position: relative; top:20px;">
|
||||
<input type="text" id="max" name="max" class="datepicker" autocomplete="off" style=" border:1px solid #d2d6de; width:250px; height: 34px;">
|
||||
|
|
|
@ -27,8 +27,9 @@
|
|||
}
|
||||
|
||||
window.onload = function() {
|
||||
ldapSelection();
|
||||
ldapSelection();
|
||||
}
|
||||
|
||||
</script>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
@ -59,21 +60,107 @@
|
|||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabs-general">
|
||||
<form role="form" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" value="general" name="config_tab" />
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="local_db_enabled" name="local_db_enabled" class="checkbox" {% if SETTING.get('local_db_enabled') %}checked{% endif %}>
|
||||
<label for="local_db_enabled">Local DB Authentication</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="signup_enabled" name="signup_enabled" class="checkbox" {% if SETTING.get('signup_enabled') %}checked{% endif %}>
|
||||
<label for="signup_enabled">Allow users to sign up</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-flat btn-primary">Save</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<form role="form" method="post" >
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" value="general" name="config_tab" />
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="local_db_enabled" name="local_db_enabled" class="checkbox" {% if SETTING.get('local_db_enabled') %}checked{% endif %}>
|
||||
<label for="local_db_enabled">Local DB Authentication</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="signup_enabled" name="signup_enabled" class="checkbox" {% if SETTING.get('signup_enabled') %}checked{% endif %}>
|
||||
<label for="signup_enabled">Allow users to sign up</label>
|
||||
</div>
|
||||
|
||||
<legend>Password Policy </br>(database authentication)</legend>
|
||||
<div class='form-check'>
|
||||
<input type="radio" class='form-check-input' id="char-based-policy" name="pass-policy-sel" value="char-based-policy" {% if SETTING.get('zxcvbn_enabled')== false %}checked{% endif %}>
|
||||
<label for="char-based-policy">Conventional password strength checks</label><br>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="radio" class='form-check-input' id="heuristic-policy" name="pass-policy-sel" value="heuristic-policy" {% if SETTING.get('zxcvbn_enabled')== true %}checked{% endif %}>
|
||||
<label for="heuristic-policy">Heuristic password strength checks (zxcvbn)</label><br>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="min_len">Minimum length</label>
|
||||
<input type="text" class="form-control char_specified" name="min_len" id="min_len" value="{{ SETTING.get('pwd_min_len') }}"
|
||||
{% if SETTING.get('zxcvbn_enabled')== true %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="min_lowercase">Minimum number of lowercase letters</label>
|
||||
<input type="text" class="form-control char_specified" name="min_lowercase" id="min_lowercase" value="{{ SETTING.get('pwd_min_lowercase') }}"
|
||||
{% if SETTING.get('zxcvbn_enabled')== true %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="min_uppercase">Minimum number of uppercase letters</label>
|
||||
<input type="text" class="form-control char_specified" name="min_uppercase" id="min_uppercase" value="{{ SETTING.get('pwd_min_uppercase') }}"
|
||||
{% if SETTING.get('zxcvbn_enabled')== true %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="min_digits">Minimum number of digits</label>
|
||||
<input type="text" class="form-control char_specified" name="min_digits" id="min_digits" value="{{ SETTING.get('pwd_min_digits') }}"
|
||||
{% if SETTING.get('zxcvbn_enabled')== true %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="min_special">Minimum number of special characters</label>
|
||||
<input type="text" class="form-control char_specified" name="min_special" id="min_special" value="{{ SETTING.get('pwd_min_special') }}"
|
||||
{% if SETTING.get('zxcvbn_enabled')== true %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="must_not_contain">Must not contain</label>
|
||||
<input type="text" class="form-control char_specified" name="must_not_contain" id="must_not_contain" placeholder="username,firstname,lastname,email" value="{{ SETTING.get('pwd_must_not_contain') }}"
|
||||
{% if SETTING.get('zxcvbn_enabled')== true %}disabled{% endif %}>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="must_not_contain">(zxcvbn)Complexity</label>
|
||||
<input type="text" class="form-control heuristic_group" name="complexity" id="complexity" value="{{ SETTING.get('zxcvbn_guesses_log') }}"
|
||||
{% if SETTING.get('zxcvbn_enabled')== false %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-flat btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<legend>Help</legend>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Conventional password <br> strength checks</dt>
|
||||
<dd>Define the password policy - character composition of the users' password
|
||||
<ul>
|
||||
<li>
|
||||
Must not contain - This field must contain the attributes which must not be part of the user's password
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
The available attributes are 'username', 'firstname', 'lastname' and 'email'.
|
||||
</li>
|
||||
<li>
|
||||
The attributes must be seperated
|
||||
by comma ',' character. (eg. 'firstname,lastname,username')
|
||||
</li>
|
||||
<li>
|
||||
Acceptable special characters are: [!@#$%^&*()_+
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>Heuristic password <br> strength checks (zxcvbn)</dt>
|
||||
<dd>Define the password complexity log factor
|
||||
<ul>
|
||||
<li>
|
||||
The default value of the log factor is 11, as it is considered secure.
|
||||
More information about the package, can be found at:
|
||||
<a href="https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_wheeler.pdf">Paper</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabs-ldap">
|
||||
|
@ -691,7 +778,33 @@
|
|||
{%- endassets %}
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
// $('#zxcvbn').on('ifChecked', function(event){
|
||||
// // alert(event.type + ' callback');
|
||||
// $(".form-control.char_specified").prop("disabled", true)
|
||||
// });
|
||||
// $('#zxcvbn').on('ifUnchecked', function(event){
|
||||
// // alert(event.type + ' callback');
|
||||
// $(".form-control.char_specified").prop("disabled", false)
|
||||
// });
|
||||
|
||||
|
||||
$('input[name="pass-policy-sel"]:radio').on('click', function() {
|
||||
if (document.getElementById('heuristic-policy').checked == true) {
|
||||
$(".form-control.char_specified").prop("disabled", true)
|
||||
$(".form-control.heuristic_group").prop("disabled", false)
|
||||
}
|
||||
else if (document.getElementById('char-based-policy').checked == true) {
|
||||
$(".form-control.char_specified").prop("disabled", false)
|
||||
$(".form-control.heuristic_group").prop("disabled", true)
|
||||
}
|
||||
})
|
||||
|
||||
$('#zxcvbn').iCheck({
|
||||
checkboxClass : 'icheckbox_square-blue',
|
||||
increaseArea : '20%'
|
||||
})
|
||||
|
||||
$(function() {
|
||||
$('#tabs').tabs({
|
||||
// add url anchor tags
|
||||
|
|
167
powerdnsadmin/templates/password_policy_macro.html
Normal file
167
powerdnsadmin/templates/password_policy_macro.html
Normal file
|
@ -0,0 +1,167 @@
|
|||
{% macro password_policy(is_logged_in, current_user, zxcvbn_enabled, pwd_min_len, pwd_min_digits, pwd_min_uppercase, pwd_min_lowercase, pwd_min_special, pwd_must_not_contain) -%}
|
||||
{{ caller() }}
|
||||
|
||||
|
||||
var csrftoken = document.getElementsByName('_csrf_token')[0].value; // $('[name=_csrf_token]').val()
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrftoken)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
{% if zxcvbn_enabled == false %}
|
||||
function charbased_password_policy(fname, lname, username, email, pass, rpass) {
|
||||
var pwd_len = parseInt("{{ pwd_min_len }}");
|
||||
var n_upper = parseInt("{{ pwd_min_uppercase }}");
|
||||
var n_lower = parseInt("{{ pwd_min_lowercase }}");
|
||||
var n_digits = parseInt("{{ pwd_min_digits }}");
|
||||
var n_special = parseInt("{{ pwd_min_special }}");
|
||||
var must_not_contain = "{{ pwd_must_not_contain }}";
|
||||
// var pattern = "^(?=(?:.*[0-9]){" + n_digits + ",})(?=(?:.*[a-z]){" + n_lower + ",})(?=(?:.*[A-Z]){" + n_upper + ",})(?=(?:.*[[!@#$%^&*()_+]){" + n_special + ",}).+$";
|
||||
// var PasswordRegEx = new RegExp(pattern, 'm');
|
||||
var upper_found = 0;
|
||||
var lower_found = 0;
|
||||
var digits_found = 0;
|
||||
var special_found = 0;
|
||||
var lower_pattern = /[a-z]/g;
|
||||
var upper_pattern = /[A-Z]/g;
|
||||
var digits_pattern = /[0-9]/g;
|
||||
var special_pattern = /[[!@#$%^&*()_+]/g;
|
||||
for (var i = 0; i < pass.length; i++) {
|
||||
if (pass[i].match(special_pattern)) special_found++;
|
||||
else if (pass[i].match(lower_pattern)) lower_found++;
|
||||
else if (pass[i].match(upper_pattern)) upper_found++;
|
||||
else if (pass[i].match(digits_pattern)) digits_found++;
|
||||
}
|
||||
var msg = "";
|
||||
if (pass.length < pwd_len) msg += 'at least ' + pwd_len + ' character(s)<br>'
|
||||
if (lower_found < n_lower) msg += 'at least ' + n_lower + ' lowercase character(s)<br>';
|
||||
if (upper_found < n_upper) msg += 'at least ' + n_upper + ' uppercase character(s)<br>';
|
||||
if (digits_found < n_digits) msg += 'at least ' + n_digits + ' digit(s)<br>';
|
||||
if (special_found < n_special) msg += 'at least ' + n_special + ' special character(s) from [!@#$%^&*()_+<br>';
|
||||
if (msg.length != 0) msg = "Password must contain: <br>" + msg;
|
||||
|
||||
// must not contain
|
||||
must_not_contain_msg = "";
|
||||
if (must_not_contain.search("username") != -1 && pass.search(username) != -1) must_not_contain_msg += " username<br>"
|
||||
if (must_not_contain.search("firstname") != -1 && pass.search(fname) != -1) must_not_contain_msg += " firstname<br>"
|
||||
if (must_not_contain.search("lastname") != -1 && pass.search(lname) != -1) must_not_contain_msg += " lastname<br>"
|
||||
if (must_not_contain.search("email") != -1 && pass.search(email) != -1) must_not_contain_msg += " email<br>"
|
||||
if (must_not_contain_msg.length != 0) must_not_contain_msg = "Password must not contain: <br>" + must_not_contain_msg
|
||||
var x = document.getElementById('policy-err');
|
||||
x.innerHTML = msg + must_not_contain_msg
|
||||
if (msg.length != 0 || must_not_contain_msg.length != 0) {
|
||||
document.getElementById('pwd-submit').disabled = true;
|
||||
}
|
||||
else if (msg.length == 0 && pass.length != 0 && rpass == pass){
|
||||
document.getElementById('pwd-submit').disabled = false;
|
||||
}
|
||||
else {
|
||||
document.getElementById('pwd-submit').disabled = true;
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
|
||||
|
||||
|
||||
var timer = null;
|
||||
var is_complex = false;
|
||||
function send_pass() {
|
||||
{% if is_logged_in == 0 %}
|
||||
var fname = document.getElementById('firstname').value;
|
||||
var lname = document.getElementById('lastname').value;
|
||||
var email = document.getElementById('email').value;
|
||||
var username = document.getElementById('username').value;
|
||||
{% else %}
|
||||
var fname = '{{current_user.firstname}}';
|
||||
var lname = '{{current_user.lastname}}';
|
||||
var email = '{{current_user.email}}';
|
||||
var username = '{{current_user.username}}';
|
||||
{% endif %}
|
||||
var password = document.getElementById('password').value;
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: "/ratepassword",
|
||||
<!-- headers: { "X-CSRFToken": getCookie("csrftoken") }, -->
|
||||
type: "post",
|
||||
data : { 'logged_in' : {{is_logged_in}} ,'fname': fname, 'lname': lname, 'email' : email, 'username' : username, 'password': password},
|
||||
success: function(response) {
|
||||
var x = document.getElementById('policy-err');
|
||||
// x.innerHTML = response['feedback'];
|
||||
x.innerHTML = "<ul>";
|
||||
for (let i = 0; i < response['feedback'].length; i++) {
|
||||
x.innerHTML += "<li>" + response['feedback'][i] + "</li>";
|
||||
}
|
||||
x.innerHTML += "</ul>"
|
||||
var strength;
|
||||
switch (response['strength']) {
|
||||
case '':
|
||||
strength = ''; // no password was given
|
||||
break;
|
||||
case 'very weak':
|
||||
strength = "<small class='progress-bar bg-danger' style='background-color: #a50021; width: 25%'>Very weak</small>";
|
||||
break;
|
||||
case 'weak':
|
||||
strength = "<small class='progress-bar bg-danger' style='background-color: #f7a73e;width: 50%'>Weak</small>";
|
||||
break;
|
||||
case 'medium':
|
||||
strength = "<small class='progress-bar bg-warning' style='background-color: #a0cb89; width: 75%'>Medium</small>";
|
||||
break;
|
||||
case 'strong':
|
||||
strength = "<small class='progress-bar bg-success' style='background-color: #2e8b57; width: 100%'>Strong</small>";
|
||||
break;
|
||||
}
|
||||
var y = document.getElementById('password-text')
|
||||
y.innerHTML = strength;
|
||||
var rpass = document.getElementById('rpassword').value;
|
||||
if (response['feedback'] != "") {
|
||||
document.getElementById('pwd-submit').disabled = true;
|
||||
} else if(!(rpass != password || password.length == 0 || rpass.length == 0)) {
|
||||
{% if is_logged_in == 1 %}
|
||||
document.getElementById('retype-err').innerHTML = '';
|
||||
{% endif %}
|
||||
document.getElementById('pwd-submit').disabled = false;
|
||||
}
|
||||
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.log("Ajax call to rate pass, has failed")
|
||||
}
|
||||
});
|
||||
timer = null; // turn the timer off
|
||||
}
|
||||
// handling password complexity requirements message
|
||||
$(':input').on('keyup', function() {
|
||||
|
||||
var pass = document.getElementById('password').value;
|
||||
|
||||
var rpass = document.getElementById('rpassword').value;
|
||||
if (rpass != pass || pass.length == 0 || rpass.length == 0) {
|
||||
{% if is_logged_in == 1 %}
|
||||
|
||||
document.getElementById('retype-err').innerHTML = 'Password confirmation does not match';
|
||||
{% endif %}
|
||||
document.getElementById('pwd-submit').disabled = true;
|
||||
}
|
||||
else {
|
||||
{% if is_logged_in == 1 %}
|
||||
|
||||
document.getElementById('retype-err').innerHTML = '';
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
var seconds = 1;
|
||||
if (timer == null) { // if user typed sth and timer is not running, then start one
|
||||
timer = setTimeout(send_pass, seconds*1000);
|
||||
}
|
||||
else { // if user typed sth and timer is still up and running,then reset timer
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
timer = setTimeout(send_pass, seconds*1000);
|
||||
}
|
||||
});
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
|
@ -20,6 +20,8 @@
|
|||
<![endif]-->
|
||||
</head>
|
||||
|
||||
{% import 'password_policy_macro.html' as password_policy_macro %}
|
||||
|
||||
<body class="hold-transition register-page">
|
||||
<div class="register-box">
|
||||
<div class="register-logo">
|
||||
|
@ -36,38 +38,41 @@
|
|||
<form action="{{ url_for('index.register') }}" method="post" data-toggle="validator">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group has-feedback">
|
||||
<input type="text" class="form-control" placeholder="First Name" name="firstname"
|
||||
<input type="text" class="form-control" placeholder="First Name" id="firstname" name="firstname"
|
||||
data-error="Please input your first name" required>
|
||||
<span class="glyphicon glyphicon-user form-control-feedback"></span>
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="text" class="form-control" placeholder="Last name" name="lastname"
|
||||
<input type="text" class="form-control" placeholder="Last name" id="lastname" name="lastname"
|
||||
data-error="Please input your last name" required>
|
||||
<span class="glyphicon glyphicon-user form-control-feedback"></span>
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="email" class="form-control" placeholder="Email" name="email"
|
||||
<input type="email" class="form-control" placeholder="Email" id="email" name="email"
|
||||
data-error="Please input your valid email address" required>
|
||||
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<p class="login-box-msg">Enter your account details below</p>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="text" class="form-control" placeholder="Username" name="username"
|
||||
<input type="text" class="form-control" placeholder="Username" id="username" name="username"
|
||||
data-error="Please input your username" required>
|
||||
<span class="glyphicon glyphicon-user form-control-feedback"></span>
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<div id="pass-feedback" class="form-group">
|
||||
<input type="password" class="form-control" placeholder="Password" id="password" name="password"
|
||||
data-error="Please input your password" required>
|
||||
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
|
||||
required>
|
||||
{% if SETTING.get('zxcvbn_enabled') == true %}
|
||||
<small class="help-block" id="password-text"></small> <br>
|
||||
{% endif %}
|
||||
<div id="policy-err" style='color: #df5948;'></div>
|
||||
</div>
|
||||
<div class="form-group has-feedback">
|
||||
<input type="password" class="form-control" placeholder="Retype password" name="rpassword"
|
||||
data-match="#password" data-match-error="Password confirmation does not match" required>
|
||||
data-match="#password" id="rpassword" data-match-error="Password confirmation does not match" required>
|
||||
<span class="glyphicon glyphicon-log-in form-control-feedback"></span>
|
||||
<span class="help-block with-errors"></span>
|
||||
</div>
|
||||
|
@ -76,7 +81,7 @@
|
|||
<button type="button" class="btn btn-flat btn-block" id="button_back">Back</button>
|
||||
</div>
|
||||
<div class="col-xs-4 pull-right">
|
||||
<button type="submit" class="btn btn-flat btn-primary btn-block">Register</button>
|
||||
<button type="submit" id="pwd-submit" class="btn btn-flat btn-primary btn-block">Register</button>
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
|
@ -103,6 +108,32 @@
|
|||
window.location.href = '{{ url_for('index.login') }}';
|
||||
})
|
||||
});
|
||||
|
||||
{% call password_policy_macro.password_policy(0, 0, SETTING.get('zxcvbn_enabled'), SETTING.get('pwd_min_len'), SETTING.get('pwd_min_digits'), SETTING.get('pwd_min_uppercase'), SETTING.get('pwd_min_lowercase'), SETTING.get('pwd_min_special'), SETTING.get('pwd_must_not_contain')) %}
|
||||
{% endcall %}
|
||||
|
||||
{% if SETTING.get('zxcvbn_enabled') == false %}
|
||||
// handling password complexity requirements message
|
||||
$(':input').on('keyup', function() {
|
||||
|
||||
var input = document.getElementById('password');
|
||||
var pass = input.value;
|
||||
|
||||
var rpass = document.getElementById('rpassword').value;
|
||||
var pass = input.value;
|
||||
if (rpass != pass || rpass.length == 0 || pass.length == 0) {
|
||||
document.getElementById('pwd-submit').disabled = true;
|
||||
}
|
||||
var fname = document.getElementById('firstname').value;
|
||||
var lname = document.getElementById('lastname').value;
|
||||
var email = document.getElementById('email').value;
|
||||
var username = document.getElementById('username').value;
|
||||
|
||||
charbased_password_policy(fname, lname, username, email, pass, rpass);
|
||||
|
||||
|
||||
});
|
||||
{% endif %}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}<title>My Profile - {{ SITE_NAME }}</title>{% endblock %}
|
||||
{% block dashboard_stat %}
|
||||
|
||||
<!-- Content Header (Page header) -->
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
|
@ -13,7 +14,10 @@
|
|||
</ol>
|
||||
</section>
|
||||
{% endblock %}
|
||||
{% import 'password_policy_macro.html' as password_policy_macro %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
|
@ -61,22 +65,39 @@
|
|||
</form>
|
||||
</div>
|
||||
{% if session['authentication_type'] == 'LOCAL' %}
|
||||
|
||||
<div class="tab-pane" id="tabs-password">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not current_user.password %}
|
||||
Your account password is managed via LDAP which isn't supported to change here.
|
||||
{% else %}
|
||||
<form action="{{ user_profile }}" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group">
|
||||
<label for="password">New Password</label> <input type="password"
|
||||
class="form-control" name="password" id="newpassword" />
|
||||
<label for="password">New Password</label>
|
||||
<input type="password"
|
||||
class="form-control" name="password" id="password" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="rpassword">Re-type New Password</label> <input type="password"
|
||||
class="form-control" name="rpassword" id="rpassword" />
|
||||
{% if zxcvbn_enabled == true %}
|
||||
<small class="help-block" id="password-text"></small> <br>
|
||||
{% endif %}
|
||||
<div id="policy-err" style='color: #df5948;'></div>
|
||||
<div class="form-group has-feedback">
|
||||
<label for="rpassword">Re-type New Password</label>
|
||||
<input type="password"
|
||||
data-match="#password" data-match-error="Password confirmation does not match"
|
||||
class="form-control" name="rpassword" id="rpassword" required />
|
||||
<span class="help-block with-errors"></span>
|
||||
<span class="glyphicon form-control-feedback"></span>
|
||||
</div>
|
||||
<div id="retype-err" style='color: #df5948;'></div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-flat btn-primary">Change Password</button>
|
||||
<button type="submit" id="pwd-submit" class="btn btn-flat btn-primary btn-block" disabled>Change Password</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
@ -128,19 +149,26 @@
|
|||
</section>
|
||||
{% endblock %}
|
||||
{% block extrascripts %}
|
||||
<!-- TODO: add password and password confirmation comparison check -->
|
||||
<script>
|
||||
$(function () {
|
||||
$('#tabs').tabs({
|
||||
// add url anchor tags
|
||||
activate: function (event, ui) {
|
||||
window.location.hash = ui.newPanel.attr('id');
|
||||
}
|
||||
{% if change_pass_tab %}
|
||||
{% if change_pass_tab == True %}
|
||||
$('#tabs a[href="#tabs-password"]').tab('show')
|
||||
{% endif %}
|
||||
{% else %}
|
||||
$(function () {
|
||||
$('#tabs').tabs({
|
||||
// add url anchor tags
|
||||
activate: function (event, ui) {
|
||||
window.location.hash = ui.newPanel.attr('id');
|
||||
}
|
||||
});
|
||||
// re-set active tab (ui)
|
||||
var activeTabIdx = $('#tabs').tabs('option', 'active');
|
||||
$('#tabs li:eq(' + activeTabIdx + ')').tab('show')
|
||||
});
|
||||
// re-set active tab (ui)
|
||||
var activeTabIdx = $('#tabs').tabs('option', 'active');
|
||||
$('#tabs li:eq(' + activeTabIdx + ')').tab('show')
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
// initialize pretty checkboxes
|
||||
$('.otp_toggle').iCheck({
|
||||
|
@ -160,5 +188,38 @@
|
|||
};
|
||||
applyChanges(postdata, $SCRIPT_ROOT + '/user/profile', false, true);
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('pwd-submit').disabled = true;
|
||||
{% call password_policy_macro.password_policy(1, user_info, zxcvbn_enabled, SETTING.get('pwd_min_len'), SETTING.get('pwd_min_digits'), SETTING.get('pwd_min_uppercase'), SETTING.get('pwd_min_lowercase'), SETTING.get('pwd_min_special'), SETTING.get('pwd_must_not_contain')) %}
|
||||
{% endcall %}
|
||||
|
||||
|
||||
{% if zxcvbn_enabled == false %}
|
||||
|
||||
// handling password complexity requirements message and password comparison
|
||||
$(':input').on('keyup', function() {
|
||||
var rpass = document.getElementById('rpassword').value;
|
||||
var input = document.getElementById('password');
|
||||
var pass = input.value;
|
||||
if (rpass != pass || pass.length == 0 || rpass.length == 0) {
|
||||
document.getElementById('pwd-submit').disabled = true;
|
||||
document.getElementById('retype-err').innerHTML = 'Password confirmation does not match';
|
||||
}
|
||||
else {
|
||||
document.getElementById('retype-err').innerHTML = '';
|
||||
}
|
||||
var fname = "{{ user_info.firstname }}";
|
||||
var lname = "{{ user_info.lastname }}";
|
||||
var email = "{{ user_info.email }}";
|
||||
var username = "{{ user_info.username }}";
|
||||
|
||||
charbased_password_policy(fname, lname, username, email, pass, rpass);
|
||||
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -28,3 +28,4 @@ PyYAML==5.4
|
|||
Flask-SSLify==0.1.5
|
||||
Flask-Mail==0.9.1
|
||||
flask-session==0.3.2
|
||||
zxcvbn==4.4.28
|
Loading…
Reference in a new issue