0001-create-password-validation-in-registration-completio.patch
src/authentic2/registration_backend/forms.py | ||
---|---|---|
115 | 115 | |
116 | 116 | |
117 | 117 |
class RegistrationCompletionForm(RegistrationCompletionFormNoPassword): |
118 |
password1 = CharField(widget=PasswordInput, label=_("Password"), |
|
118 |
class Media: |
|
119 |
js = ('authentic2/js/password.js',) |
|
120 | ||
121 |
password1 = CharField(widget=PasswordInput(attrs={'class': 'validatePassword showPassword passwordEquality'}), |
|
122 |
label=_("Password"), |
|
119 | 123 |
validators=[validators.validate_password], |
120 |
help_text=validators.password_help_text()) |
|
121 |
password2 = CharField(widget=PasswordInput, label=_("Password (again)")) |
|
124 |
help_text='%s <span class="a2-policy-block">%s</span>' % ('<i class="show-password-button" aria-hidden="true"></i>', validators.password_help_text())) |
|
125 |
password2 = CharField(widget=PasswordInput(attrs={'class': 'passwordEquality'}), |
|
126 |
label=_("Password (again)"), |
|
127 |
help_text='<span class="a2-passwords-matched">%s</span><span class="a2-passwords-unmatched">%s</span>' % \ |
|
128 |
(ugettext('Passwords match.'), ugettext('Passwords does not match.'))) |
|
122 | 129 | |
123 | 130 |
def clean(self): |
124 | 131 |
""" |
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-policy-block { |
|
81 |
display: block; |
|
82 |
} |
|
83 | ||
84 |
.a2-min-class-policy, .a2-min-length-policy, .a2-regexp-policy { |
|
85 |
display: inline; |
|
86 |
} |
|
87 | ||
88 |
.password-error { |
|
89 |
font-weight: bold; |
|
90 |
color: red; |
|
91 |
} |
|
92 | ||
93 |
.password-error:before { |
|
94 |
content: "\f071"; |
|
95 |
margin-right: 0.3em; |
|
96 |
font-family: FontAwesome; |
|
97 |
font-size: 100%; |
|
98 |
color: red; |
|
99 |
} |
|
100 | ||
101 |
.password-ok:before { |
|
102 |
content: "\f00c"; |
|
103 |
font-family: FontAwesome; |
|
104 |
font-size: 100%; |
|
105 |
color: green; |
|
106 |
} |
|
107 | ||
108 |
.showPassword ~ .helptext{ |
|
109 |
display: inline; |
|
110 |
margin-left: 0.5em; |
|
111 |
} |
|
112 | ||
113 |
.show-password-button { |
|
114 |
cursor: pointer; |
|
115 |
} |
|
116 | ||
117 |
.show-password-button:after { |
|
118 |
content: "\f06e"; |
|
119 |
font-family: FontAwesome; |
|
120 |
font-size: 150%; |
|
121 |
} |
|
122 | ||
123 |
.hide-password-button:after { |
|
124 |
content: "\f070"; |
|
125 |
font-family: FontAwesome; |
|
126 |
font-size: 150%; |
|
127 |
} |
|
128 | ||
129 |
.a2-passwords-unmatched { |
|
130 |
display: none; |
|
131 |
color: red; |
|
132 |
} |
|
133 | ||
134 |
.a2-passwords-matched { |
|
135 |
display: none; |
|
136 |
color: green; |
|
137 |
} |
|
138 | ||
139 |
.password-error ~ .helptext .a2-passwords-unmatched { |
|
140 |
display: block; |
|
141 |
} |
|
142 | ||
143 |
.password-ok ~ .helptext .a2-passwords-matched { |
|
144 |
display: block; |
|
145 |
} |
src/authentic2/static/authentic2/js/password.js | ||
---|---|---|
1 |
"use strict"; |
|
2 |
/* globals $, console, window */ |
|
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 minClassElt = $(this).find('.a2-min-class-policy'); |
|
16 |
var minLengthElt = $(this).find('.a2-min-length-policy'); |
|
17 |
var regexpElt = $(this).find('.a2-regexp-policy'); |
|
18 |
$(this) |
|
19 |
.find('.validatePassword') |
|
20 |
.each(function () { |
|
21 |
var password = $(this).val(); |
|
22 |
var min_len = settings.A2_PASSWORD_POLICY_MIN_LENGTH; |
|
23 |
if (min_len && password.length < min_len) { |
|
24 |
toggleError(minLengthElt); |
|
25 |
} else { |
|
26 |
toggleOk(minLengthElt); |
|
27 |
} |
|
28 |
var digits = /\d/g; |
|
29 |
var lowerCase = /[a-z]/g; |
|
30 |
var upperCase = /[A-Z]/g; |
|
31 |
var punctuation = /'!"#\$%&\\'\(\)\*\+,-\.\/:;<=>\?@\[\]\^_`\{\|\}~'/g; |
|
32 |
var minClassCount = settings.A2_PASSWORD_POLICY_MIN_CLASSES; |
|
33 |
var classCount = 0; |
|
34 |
if (minClassCount) { |
|
35 |
[digits, lowerCase, upperCase, punctuation].forEach(function (cls) { |
|
36 |
if (cls.test(password)) classCount++; |
|
37 |
}) |
|
38 |
if (classCount < minClassCount) { |
|
39 |
toggleError(minClassElt); |
|
40 |
} else { |
|
41 |
toggleOk(minClassElt); |
|
42 |
} |
|
43 |
} |
|
44 |
if (settings.A2_PASSWORD_POLICY_REGEX) { |
|
45 |
var regExpObject = new RegExp(settings.A2_PASSWORD_POLICY_REGEX, 'g'); |
|
46 |
if (!regExpObject.test(password)) { |
|
47 |
toggleError(regexpElt); |
|
48 |
} else { |
|
49 |
toggleOk(regexpElt); |
|
50 |
} |
|
51 |
} |
|
52 |
}); |
|
53 |
} |
|
54 |
var passwordEquality = function () { |
|
55 |
var form = $(this).parents('form'); |
|
56 |
var input0 = form.find('.passwordEquality').eq(0); |
|
57 |
var input1 = form.find('.passwordEquality').eq(1); |
|
58 |
if (!input1.val() || !input0.val()) return; |
|
59 |
if (input0.val() !== input1.val()) { |
|
60 |
toggleError(input1); |
|
61 |
} else { |
|
62 |
toggleOk(input1); |
|
63 |
} |
|
64 |
} |
|
65 |
var showPassword = function () { |
|
66 |
$(this).addClass('hide-password-button'); |
|
67 |
$(this) |
|
68 |
.parents('form') |
|
69 |
.find('input.showPassword') |
|
70 |
.attr('type', 'text'); |
|
71 |
} |
|
72 |
var hidePassword = function () { |
|
73 |
$(this).removeClass('hide-password-button'); |
|
74 |
$(this) |
|
75 |
.parents('form') |
|
76 |
.find('input.showPassword') |
|
77 |
.attr('type', 'password'); |
|
78 |
} |
|
79 |
// TODO check both passwords are equal |
|
80 |
$('form[data-password-policy]') |
|
81 |
.keyup(validatePassword); |
|
82 |
$('form[data-password-policy] input[type=password].passwordEquality') |
|
83 |
.keyup(passwordEquality); |
|
84 |
$('form.showPassword') |
|
85 |
.on('mousedown', '.show-password-button', showPassword); |
|
86 |
$('form.showPassword') |
|
87 |
.on('mouseup', '.show-password-button', hidePassword); |
|
88 |
}); |
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 %}'> |
|
29 | 30 |
{% csrf_token %} |
30 | 31 |
{{ form.as_p }} |
31 | 32 |
<button class="submit-button">{% trans 'Submit' %}</button> |
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 |
- |