Browse Source

Password strength meter and length check in the "Set Password" form

pull/248/head
Pēteris Caune 6 years ago
parent
commit
23b197526c
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
8 changed files with 149 additions and 13 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +1
    -1
      hc/accounts/forms.py
  3. +49
    -0
      hc/accounts/tests/test_set_password.py
  4. +37
    -0
      static/css/set_password.css
  5. +8
    -0
      static/js/set-password.js
  6. +28
    -0
      static/js/zxcvbn.js
  7. +24
    -12
      templates/accounts/set_password.html
  8. +1
    -0
      templates/base.html

+ 1
- 0
CHANGELOG.md View File

@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
- Can configure the email integration to only report the "down" events (#231) - Can configure the email integration to only report the "down" events (#231)
- Add "Test!" function in the Integrations page (#207) - Add "Test!" function in the Integrations page (#207)
- Rate limiting for the log in attempts - Rate limiting for the log in attempts
- Password strength meter and length check in the "Set Password" form
## 1.6.0 - 2019-04-01 ## 1.6.0 - 2019-04-01


+ 1
- 1
hc/accounts/forms.py View File

@ -76,7 +76,7 @@ class ReportSettingsForm(forms.Form):
class SetPasswordForm(forms.Form): class SetPasswordForm(forms.Form):
password = forms.CharField()
password = forms.CharField(min_length=8)
class ChangeEmailForm(forms.Form): class ChangeEmailForm(forms.Form):


+ 49
- 0
hc/accounts/tests/test_set_password.py View File

@ -0,0 +1,49 @@
from hc.test import BaseTestCase
class SetPasswordTestCase(BaseTestCase):
def test_it_shows_form(self):
token = self.profile.prepare_token("set-password")
self.client.login(username="[email protected]", password="password")
r = self.client.get("/accounts/set_password/%s/" % token)
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Please pick a password")
def test_it_checks_token(self):
self.profile.prepare_token("set-password")
self.client.login(username="[email protected]", password="password")
# GET
r = self.client.get("/accounts/set_password/invalid-token/")
self.assertEqual(r.status_code, 400)
# POST
r = self.client.post("/accounts/set_password/invalid-token/")
self.assertEqual(r.status_code, 400)
def test_it_sets_password(self):
token = self.profile.prepare_token("set-password")
self.client.login(username="[email protected]", password="password")
payload = {"password": "correct horse battery staple"}
r = self.client.post("/accounts/set_password/%s/" % token, payload)
self.assertEqual(r.status_code, 302)
old_password = self.alice.password
self.alice.refresh_from_db()
self.assertNotEqual(self.alice.password, old_password)
def test_post_checks_length(self):
token = self.profile.prepare_token("set-password")
self.client.login(username="[email protected]", password="password")
payload = {"password": "abc"}
r = self.client.post("/accounts/set_password/%s/" % token, payload)
self.assertEqual(r.status_code, 200)
old_password = self.alice.password
self.alice.refresh_from_db()
self.assertEqual(self.alice.password, old_password)

+ 37
- 0
static/css/set_password.css View File

@ -0,0 +1,37 @@
#set-password-group #password {
border-color: #ddd;
box-shadow: none;
}
#meter {
margin: 3px 0 20px 0;
display: flex;
}
#meter div {
background: #ddd;
height: 5px;
flex: 1;
border-radius: 2px;
margin-right: 3px;
}
#meter div:last-child {
margin-right: 0;
}
#meter.score-1 .s1 {
background: #FF5252;
}
#meter.score-2 .s2 {
background: #FFAB40;
}
#meter.score-3 .s3 {
background: #9CCC65;
}
#meter.score-4 .s4 {
background: #22bc66;;
}

+ 8
- 0
static/js/set-password.js View File

@ -0,0 +1,8 @@
$(function () {
$pw = $("#password");
$meter = $("#meter");
$pw.on("input", function() {
var result = zxcvbn($pw.val());
$meter.attr("class", "score-" + result.score);
});
});

+ 28
- 0
static/js/zxcvbn.js
File diff suppressed because it is too large
View File


+ 24
- 12
templates/accounts/set_password.html View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load hc_extras %}
{% load compress hc_extras static %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
@ -15,17 +15,20 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<div class="form-group">
<div class="input-group input-group-lg">
<div class="input-group-addon">
<span class="icon-dots"></span>
</div>
<input
type="password"
class="form-control"
name="password"
placeholder="pick a password">
</div>
<div id="set-password-group" class="form-group">
<input
id="password"
type="password"
minlength="8"
class="form-control input-lg"
name="password"
placeholder="pick a password (at least 8 characters)">
<div id="meter">
<div class="s1 s2 s3 s4"></div>
<div class="s2 s3 s4"></div>
<div class="s3 s4"></div>
<div class="s4"></div>
</table>
</div> </div>
<div class="clearfix"> <div class="clearfix">
@ -38,3 +41,12 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block scripts %}
{% compress js %}
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/zxcvbn.js' %}"></script>
<script src="{% static 'js/set-password.js' %}"></script>
{% endcompress %}
{% endblock %}

+ 1
- 0
templates/base.html View File

@ -46,6 +46,7 @@
<link rel="stylesheet" href="{% static 'css/snippet-copy.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/snippet-copy.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/syntax.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/syntax.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/welcome.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/welcome.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/set_password.css' %}" type="text/css">
{% endcompress %} {% endcompress %}
</head> </head>
<body class="page-{{ page }}"> <body class="page-{{ page }}">


Loading…
Cancel
Save