0001-add-password-validation-UI-in-registration-view-2443.patch
src/authentic2/registration_backend/forms.py | ||
---|---|---|
115 | 115 | |
116 | 116 | |
117 | 117 |
class RegistrationCompletionForm(RegistrationCompletionFormNoPassword): |
118 |
class Media: |
|
119 |
js = ('authentic2/js/password.js',) |
|
120 | ||
118 | 121 |
password1 = CharField(widget=PasswordInput, label=_("Password"), |
119 | 122 |
validators=[validators.validate_password], |
120 | 123 |
help_text=validators.password_help_text()) |
src/authentic2/registration_backend/views.py | ||
---|---|---|
2 | 2 |
import collections |
3 | 3 |
import logging |
4 | 4 |
import random |
5 |
import json |
|
5 | 6 | |
6 | 7 |
from django.conf import settings |
7 | 8 |
from django.shortcuts import get_object_or_404 |
... | ... | |
266 | 267 |
ctx['email'] = self.email |
267 | 268 |
ctx['email_is_unique'] = self.email_is_unique |
268 | 269 |
ctx['create'] = 'create' in self.request.GET |
270 |
ctx['passwordpolicy'] = json.dumps({ |
|
271 |
'A2_PASSWORD_POLICY_MIN_LENGTH': app_settings.A2_PASSWORD_POLICY_MIN_LENGTH, |
|
272 |
'A2_PASSWORD_POLICY_MIN_CLASSES': app_settings.A2_PASSWORD_POLICY_MIN_CLASSES, |
|
273 |
'A2_PASSWORD_POLICY_REGEX': app_settings.A2_PASSWORD_POLICY_REGEX, |
|
274 |
}) |
|
269 | 275 |
return ctx |
270 | 276 | |
271 | 277 |
def get(self, request, *args, **kwargs): |
src/authentic2/static/authentic2/css/style.css | ||
---|---|---|
76 | 76 |
.a2-log-message { |
77 | 77 |
white-space: pre-wrap; |
78 | 78 |
} |
79 | ||
80 |
.a2-min-class-policy, .a2-min-length-policy, .a2-regexp-policy { |
|
81 |
display: inline; |
|
82 |
} |
|
83 | ||
84 |
.password-error { |
|
85 |
font-weight: bold; |
|
86 |
color: red; |
|
87 |
} |
|
88 | ||
89 |
.password-error:before { |
|
90 |
content: "\f071"; |
|
91 |
margin-right: 0.3em; |
|
92 |
font-family: FontAwesome; |
|
93 |
font-size: 100%; |
|
94 |
color: red; |
|
95 |
} |
|
96 | ||
97 |
.password-ok:before { |
|
98 |
content: "\f00c"; |
|
99 |
font-family: FontAwesome; |
|
100 |
font-size: 100%; |
|
101 |
color: green; |
|
102 |
} |
|
103 | ||
104 |
.show-password-button { |
|
105 |
padding-left: 0.5em; |
|
106 |
cursor: pointer; |
|
107 |
} |
|
108 | ||
109 |
.show-password-button:after { |
|
110 |
content: "\f06e"; |
|
111 |
font-family: FontAwesome; |
|
112 |
font-size: 150%; |
|
113 |
} |
|
114 | ||
115 |
.hide-password-button:after { |
|
116 |
content: "\f070"; |
|
117 |
font-family: FontAwesome; |
|
118 |
font-size: 150%; |
|
119 |
} |
|
120 | ||
121 |
.a2-passwords-unmatched { |
|
122 |
display: none; |
|
123 |
color: red; |
|
124 |
} |
|
125 | ||
126 |
.a2-passwords-matched { |
|
127 |
display: none; |
|
128 |
color: green; |
|
129 |
} |
|
130 | ||
131 |
.password-error .a2-passwords-unmatched { |
|
132 |
display: inline; |
|
133 |
} |
|
134 | ||
135 |
.password-ok .a2-passwords-matched { |
|
136 |
display: inline; |
|
137 |
} |
src/authentic2/static/authentic2/js/password.js | ||
---|---|---|
1 |
"use strict"; |
|
2 |
/* globals $ */ |
|
3 | ||
4 |
$(function () { |
|
5 |
var toggleError = function($elt) { |
|
6 |
$elt.removeClass('password-ok'); |
|
7 |
$elt.addClass('password-error'); |
|
8 |
} |
|
9 |
var toggleOk = function($elt) { |
|
10 |
$elt.removeClass('password-error'); |
|
11 |
$elt.addClass('password-ok'); |
|
12 |
} |
|
13 |
var validatePassword = function () { |
|
14 |
var settings = $(this).data('passwordPolicy'); |
|
15 |
var inputName = $(this).data('passwordPolicyInputName'); |
|
16 |
var minClassElt = $(this).find('.a2-min-class-policy'); |
|
17 |
var minLengthElt = $(this).find('.a2-min-length-policy'); |
|
18 |
var regexpElt = $(this).find('.a2-regexp-policy'); |
|
19 |
$(this) |
|
20 |
.find('input[name='+inputName+']') |
|
21 |
.each(function () { |
|
22 |
var password = $(this).val(); |
|
23 |
var min_len = settings.A2_PASSWORD_POLICY_MIN_LENGTH; |
|
24 |
if (min_len && password.length < min_len) { |
|
25 |
toggleError(minLengthElt); |
|
26 |
} else { |
|
27 |
toggleOk(minLengthElt); |
|
28 |
} |
|
29 |
var digits = /\d/g; |
|
30 |
var lowerCase = /[a-z]/g; |
|
31 |
var upperCase = /[A-Z]/g; |
|
32 |
var punctuation = /'!"#\$%&\\'\(\)\*\+,-\.\/:;<=>\?@\[\]\^_`\{\|\}~'/g; |
|
33 |
var minClassCount = settings.A2_PASSWORD_POLICY_MIN_CLASSES; |
|
34 |
var classCount = 0; |
|
35 |
if (minClassCount) { |
|
36 |
[digits, lowerCase, upperCase, punctuation].forEach(function (cls) { |
|
37 |
if (cls.test(password)) classCount++; |
|
38 |
}) |
|
39 |
if (classCount < minClassCount) { |
|
40 |
toggleError(minClassElt); |
|
41 |
} else { |
|
42 |
toggleOk(minClassElt); |
|
43 |
} |
|
44 |
} |
|
45 |
if (settings.A2_PASSWORD_POLICY_REGEX) { |
|
46 |
var regExpObject = new RegExp(settings.A2_PASSWORD_POLICY_REGEX, 'g'); |
|
47 |
if (!regExpObject.test(password)) { |
|
48 |
toggleError(regexpElt); |
|
49 |
} else { |
|
50 |
toggleOk(regexpElt); |
|
51 |
} |
|
52 |
} |
|
53 |
}); |
|
54 |
} |
|
55 |
var passwordEquality = function () { |
|
56 |
var form = $(this).parents('form'); |
|
57 |
var messages = form.find('.a2-password-messages'); |
|
58 |
var input0 = form.find('input[type=password]').eq(0); |
|
59 |
var input1 = form.find('input[type=password]').eq(1); |
|
60 |
if (!input1.val() || !input0.val()) return; |
|
61 |
if (input0.val() !== input1.val()) { |
|
62 |
toggleError(messages); |
|
63 |
} else { |
|
64 |
toggleOk(messages); |
|
65 |
} |
|
66 |
} |
|
67 |
var showPassword = function () { |
|
68 |
$(this).addClass('hide-password-button'); |
|
69 |
$(this) |
|
70 |
.parents('form') |
|
71 |
.find('input.showPassword') |
|
72 |
.attr('type', 'text'); |
|
73 |
} |
|
74 |
var hidePassword = function () { |
|
75 |
$(this).removeClass('hide-password-button'); |
|
76 |
$(this) |
|
77 |
.parents('form') |
|
78 |
.find('input.showPassword') |
|
79 |
.attr('type', 'password'); |
|
80 |
} |
|
81 | ||
82 |
$('form[data-password-policy]') |
|
83 |
.keyup(validatePassword); |
|
84 |
$('form.passwordEquality input[type=password]') |
|
85 |
.keyup(passwordEquality); |
|
86 |
/* add the show-password-button after the first input */ |
|
87 |
$('<i class="show-password-button"></i>') |
|
88 |
.insertAfter($('form.showPassword input[name='+ |
|
89 |
$('form.showPassword').data('passwordShowInputName') |
|
90 |
+']').addClass('showPassword')); |
|
91 |
$('form.showPassword') |
|
92 |
.on('mousedown', '.show-password-button', showPassword); |
|
93 |
$('form.showPassword') |
|
94 |
.on('mouseup', '.show-password-button', hidePassword); |
|
95 |
}); |
src/authentic2/templates/registration/registration_completion_form.html | ||
---|---|---|
25 | 25 |
{% block content %} |
26 | 26 |
<h2>{% trans "Registration" %}</h2> |
27 | 27 |
<p>{% trans "Please fill the form to complete your registration" %}</p> |
28 |
<form method="post"> |
|
28 |
<form method="post" class="showPassword passwordEquality" |
|
29 |
data-password-policy='{% autoescape off %}{{ passwordpolicy }}{% endautoescape %}' |
|
30 |
data-password-policy-input-name='password1' |
|
31 |
data-password-show-input-name='password1'> |
|
29 | 32 |
{% csrf_token %} |
30 | 33 |
{{ form.as_p }} |
34 |
<div class="a2-password-messages"> |
|
35 |
<span class="a2-passwords-matched">{% trans 'Passwords match.' %}</span> |
|
36 |
<span class="a2-passwords-unmatched">{% trans 'Passwords do not match.' %}</span> |
|
37 |
</div> |
|
31 | 38 |
<button class="submit-button">{% trans 'Submit' %}</button> |
32 | 39 |
</form> |
33 | 40 |
{% endblock %} |
src/authentic2/validators.py | ||
---|---|---|
120 | 120 | |
121 | 121 | |
122 | 122 |
def __password_help_text_helper(): |
123 |
if app_settings.A2_PASSWORD_POLICY_MIN_LENGTH and \ |
|
124 |
app_settings.A2_PASSWORD_POLICY_MIN_CLASSES: |
|
125 |
yield ugettext('Your password must contain at least %(min_length)d characters from at ' |
|
126 |
'least %(min_classes)d classes among: lowercase letters, uppercase letters, ' |
|
127 |
'digits and punctuations.') % { |
|
128 |
'min_length': app_settings.A2_PASSWORD_POLICY_MIN_LENGTH, |
|
129 |
'min_classes': app_settings.A2_PASSWORD_POLICY_MIN_CLASSES} |
|
123 |
''' |
|
124 |
Password fields help_text |
|
125 |
''' |
|
126 |
min_length_html = '<span class="a2-min-length-policy">%s</span>' %\ |
|
127 |
ugettext('Your password must contain at least %(min_length)d characters.' % |
|
128 |
{'min_length': app_settings.A2_PASSWORD_POLICY_MIN_LENGTH}) |
|
129 | ||
130 |
min_class_html = '<span class="a2-min-class-policy">%s</span>' %\ |
|
131 |
ugettext(('at least %(min_classes)d classes among: lowercase letters, uppercase letters, digits and punctuations.') % |
|
132 |
{'min_classes': app_settings.A2_PASSWORD_POLICY_MIN_CLASSES}) |
|
133 |
if app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG: |
|
134 |
regexp_html = '<span class="a2-regexp-policy">%s</span>' %\ |
|
135 |
ugettext(app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG) |
|
136 |
else: |
|
137 |
regexp_html = '<span class="a2-regexp-policy">%s</span>' %\ |
|
138 |
ugettext('Your password must match the regular expression: %(regexp)s, please change this message using the A2_PASSWORD_POLICY_REGEX_ERROR_MSG setting.') % \ |
|
139 |
{'regexp': app_settings.A2_PASSWORD_POLICY_REGEX} |
|
140 | ||
141 |
if app_settings.A2_PASSWORD_POLICY_MIN_LENGTH and app_settings.A2_PASSWORD_POLICY_MIN_CLASSES: |
|
142 |
yield '%s %s %s' % (min_length_html, _('from'), min_class_html) |
|
130 | 143 |
else: |
131 | 144 |
if app_settings.A2_PASSWORD_POLICY_MIN_LENGTH: |
132 |
yield ugettext('Your password must contain at least %(min_length)d characters.') % {'min_length': app_settings.A2_PASSWORD_POLICY_MIN_LENGTH}
|
|
145 |
yield min_length_html
|
|
133 | 146 |
if app_settings.A2_PASSWORD_POLICY_MIN_CLASSES: |
134 |
yield ugettext('Your password must contain characters from at least %(min_classes)d ' |
|
135 |
'classes among: lowercase letters, uppercase letters, digits ' |
|
136 |
'and punctuations.') % {'min_classes': app_settings.A2_PASSWORD_POLICY_MIN_CLASSES} |
|
147 |
yield "%s %s" % (ugettext('Your password must contain characters from'), min_class_html) |
|
137 | 148 |
if app_settings.A2_PASSWORD_POLICY_REGEX: |
138 |
yield ugettext(app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG) or \ |
|
139 |
ugettext('Your password must match the regular expression: ' |
|
140 |
'%(regexp)s, please change this message using the ' |
|
141 |
'A2_PASSWORD_POLICY_REGEX_ERROR_MSG setting.') % \ |
|
142 |
{'regexp': app_settings.A2_PASSWORD_POLICY_REGEX} |
|
149 |
yield regexp_html |
|
150 | ||
143 | 151 | |
144 | 152 |
def password_help_text(): |
145 | 153 |
return ' '.join(__password_help_text_helper()) |
146 |
- |