Skip to content

Commit f570e07

Browse files
Yeray Diaz DiazYeray Diaz Diaz
Yeray Diaz Diaz
authored and
Yeray Diaz Diaz
committed
Port password strength gauge to Stimulus
1 parent 2fa28ca commit f570e07

File tree

6 files changed

+63
-57
lines changed

6 files changed

+63
-57
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
/* global zxcvbn */
16+
17+
import { Controller } from "stimulus";
18+
19+
export default class extends Controller {
20+
static targets = ["password", "strengthGauge"];
21+
22+
checkPasswordStrength() {
23+
let password = this.passwordTarget.value;
24+
if (password === "") {
25+
this.strengthGaugeTarget.setAttribute("class", "password-strength__gauge");
26+
this.setScreenReaderMessage("Password field is empty");
27+
} else {
28+
// following recommendations on the zxcvbn JS docs
29+
// the zxcvbn function is available by loading `vendor/zxcvbn.js`
30+
// in the register, account and reset password templates
31+
let zxcvbnResult = zxcvbn(password);
32+
this.strengthGaugeTarget.setAttribute("class", `password-strength__gauge password-strength__gauge--${zxcvbnResult.score}`);
33+
this.strengthGaugeTarget.setAttribute("data-zxcvbn-score", zxcvbnResult.score);
34+
this.setScreenReaderMessage(zxcvbnResult.feedback.suggestions.join(" ") || "Password is strong");
35+
}
36+
}
37+
38+
setScreenReaderMessage(msg) {
39+
this.strengthGaugeTarget.querySelector(".sr-only").innerHTML = msg;
40+
}
41+
}

warehouse/static/js/warehouse/utils/forms.js

Lines changed: 11 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -50,50 +50,24 @@ export function submitTriggers() {
5050
}
5151
}
5252

53-
/* global zxcvbn */
54-
5553
const tooltipClasses = ["tooltipped", "tooltipped-s", "tooltipped-immediate"];
5654
let passwordFormRoot = document;
5755

58-
const passwordStrengthValidator = (value) => {
59-
const zxcvbnResult = zxcvbn(value);
60-
return zxcvbnResult.score < 2 ?
61-
zxcvbnResult.feedback.suggestions.join(" ") : null;
56+
const passwordStrengthValidator = () => {
57+
let passwordGauge = document.querySelector(".password-strength__gauge");
58+
let score = parseInt(passwordGauge.getAttribute("data-zxcvbn-score"));
59+
if (!isNaN(score) && score < 2) {
60+
return passwordGauge.querySelector(".sr-only").innerHTML;
61+
} else {
62+
return null;
63+
}
6264
};
6365

6466
const fieldRequiredValidator = (value) => {
6567
return value === ""?
6668
"Please fill out this field" : null;
6769
};
6870

69-
const checkPasswordStrength = (event) => {
70-
let result = passwordFormRoot.querySelector(".password-strength__gauge");
71-
if (event.target.value === "") {
72-
result.setAttribute("class", "password-strength__gauge");
73-
// Feedback for screen readers
74-
result.querySelector(".sr-only").innerHTML = "Password field is empty";
75-
} else {
76-
// following recommendations on the zxcvbn JS docs
77-
// the zxcvbn function is available by loading `vendor/zxcvbn.js`
78-
// in the register page template only
79-
let zxcvbnResult = zxcvbn(event.target.value);
80-
result.setAttribute("class", `password-strength__gauge password-strength__gauge--${zxcvbnResult.score}`);
81-
82-
// Feedback for screen readers
83-
result.querySelector(".sr-only").innerHTML = zxcvbnResult.feedback.suggestions.join(" ") || "Password is strong";
84-
}
85-
};
86-
87-
const setupPasswordStrengthGauge = () => {
88-
let password = passwordFormRoot.querySelector("#new_password");
89-
if (password === null) return;
90-
password.addEventListener(
91-
"input",
92-
checkPasswordStrength,
93-
false
94-
);
95-
};
96-
9771
const attachTooltip = (field, message) => {
9872
let parentNode = field.parentNode;
9973
parentNode.classList.add.apply(parentNode.classList, tooltipClasses);
@@ -121,14 +95,6 @@ const validateForm = (event) => {
12195
}
12296

12397
let password = passwordFormRoot.querySelector("#new_password");
124-
let passwordConfirm = passwordFormRoot.querySelector("#password_confirm");
125-
if (password.value !== passwordConfirm.value) {
126-
let message = "Passwords do not match";
127-
attachTooltip(password, message);
128-
event.preventDefault();
129-
return false;
130-
}
131-
13298
let passwordStrengthMessage = passwordStrengthValidator(password.value);
13399
if (passwordStrengthMessage !== null) {
134100
attachTooltip(password, passwordStrengthMessage);
@@ -138,12 +104,11 @@ const validateForm = (event) => {
138104
};
139105

140106
export function registerFormValidation() {
141-
const passwordStrengthNode = document.querySelector(".password-strength");
142-
if (passwordStrengthNode === null) return;
107+
const newPasswordNode = document.querySelector("#new_password");
108+
if (newPasswordNode === null) return;
143109
passwordFormRoot = document.evaluate(
144-
"./ancestor::form", passwordStrengthNode, null,
110+
"./ancestor::form", newPasswordNode, null,
145111
XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
146-
setupPasswordStrengthGauge();
147112
const submitButton = passwordFormRoot.querySelector("input[type='submit']");
148113
submitButton.addEventListener("click", validateForm, false);
149114
}

warehouse/templates/accounts/register.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<div class="site-container">
2222
<h1 class="page-title">Create an account on PyPI</h1>
2323

24-
<form method="POST" action="{{ request.current_route_path() }}" data-controller="password password-match">
24+
<form method="POST" action="{{ request.current_route_path() }}" data-controller="password password-match password-strength-gauge">
2525
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
2626

2727
{% if form.errors.__all__ %}
@@ -85,7 +85,7 @@ <h1 class="page-title">Create an account on PyPI</h1>
8585
</div>
8686
{# the password field needs to be wrapped in a div to properly place tooltips #}
8787
<div>
88-
{{ form.new_password(placeholder="Select a password", required="required", class_="form-group__input", autocomplete="new-password", data_target="password.password password-match.passwordMatch", data_action="keyup->password-match#checkPasswordsMatch") }}
88+
{{ form.new_password(placeholder="Select a password", required="required", class_="form-group__input", autocomplete="new-password", data_target="password.password password-match.passwordMatch password-strength-gauge.password", data_action="keyup->password-match#checkPasswordsMatch keyup->password-strength-gauge#checkPasswordStrength") }}
8989
</div>
9090
{% if form.new_password.errors %}
9191
<ul class="form-errors">
@@ -94,7 +94,7 @@ <h1 class="page-title">Create an account on PyPI</h1>
9494
{% endfor %}
9595
</ul>
9696
{% endif %}
97-
{{ password_strength_gauge() }}
97+
{{ password_strength_gauge(data_target="password-strength-gauge.strengthGauge") }}
9898
</div>
9999

100100
<div class="form-group">

warehouse/templates/accounts/reset-password.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<section class="horizontal-section">
2020
<div class="site-container">
2121
<h1 class="page-title">Reset Your Password</h1>
22-
<form data-controller="password password-match" method="POST" action="{{ request.current_route_path() }}">
22+
<form data-controller="password password-match password-strength-gauge" method="POST" action="{{ request.current_route_path() }}">
2323
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
2424
{% if form.errors.__all__ %}
2525
<ul class="errors">
@@ -39,7 +39,7 @@ <h1 class="page-title">Reset Your Password</h1>
3939
</div>
4040
{# the password field needs to be wrapped in a div to properly place tooltips #}
4141
<div>
42-
{{ form.new_password(placeholder="Select a new password", required="required", class_="form-group__input", data_target="password.password password-match.passwordMatch", data_action="keyup->password-match#checkPasswordsMatch") }}
42+
{{ form.new_password(placeholder="Select a new password", required="required", class_="form-group__input", data_target="password.password password-match.passwordMatch password-strength-gauge.password", data_action="keyup->password-match#checkPasswordsMatch keyup->password-strength-gauge#checkPasswordStrength") }}
4343
</div>
4444
{% if form.new_password.errors %}
4545
<ul class="form-errors">
@@ -48,7 +48,7 @@ <h1 class="page-title">Reset Your Password</h1>
4848
{% endfor %}
4949
</ul>
5050
{% endif %}
51-
{{ password_strength_gauge() }}
51+
{{ password_strength_gauge(data_target="password-strength-gauge.strengthGauge") }}
5252
</div>
5353

5454
<div class="form-group">

warehouse/templates/base.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
</time>
1919
{%- endmacro %}
2020

21-
{% macro password_strength_gauge() -%}
21+
{% macro password_strength_gauge(data_target=None) -%}
2222
<p class="form-group__help-text">
2323
Choose a strong password that contains letters (uppercase and lowercase), numbers and special characters. Avoid common words or repetition.
2424
</p>
2525
<p class="form-group__help-text">
2626
<strong>Password strength:</strong>
2727
<span class="password-strength">
28-
<span class="password-strength__gauge">
28+
<span class="password-strength__gauge"{% if data_target %} data-target="{{ data_target }}"{% endif %}>
2929
<span class="sr-only">Password field is empty</span>
3030
</span>
3131
</span>

warehouse/templates/manage/account.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ <h2 class="sub-title">Account Emails</h2>
203203

204204
<h2 class="sub-title">Change Password</h2>
205205
{{ form_error_anchor(change_password_form) }}
206-
<form data-controller="password password-match" method="POST" action="#errors">
206+
<form data-controller="password password-match password-strength-gauge" method="POST" action="#errors">
207207
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
208208
{{ form_errors(change_password_form) }}
209209
<div class="form-group">
@@ -221,10 +221,10 @@ <h2 class="sub-title">Change Password</h2>
221221
<label class="form-group__label" for="name">New Password</label>
222222
{# the password field needs to be wrapped in a div to properly place tooltips #}
223223
<div>
224-
{{ change_password_form.new_password(placeholder="Select a new password", required="required", class_="form-group__input", data_target="password.password password-match.passwordMatch", data_action="keyup->password-match#checkPasswordsMatch") }}
224+
{{ change_password_form.new_password(placeholder="Select a new password", required="required", class_="form-group__input", data_target="password.password password-match.passwordMatch password-strength-gauge.password", data_action="keyup->password-match#checkPasswordsMatch keyup->password-strength-gauge#checkPasswordStrength") }}
225225
</div>
226226
{{ field_errors(change_password_form.new_password) }}
227-
{{ password_strength_gauge() }}
227+
{{ password_strength_gauge(data_target="password-strength-gauge.strengthGauge") }}
228228
</div>
229229
<div class="form-group">
230230
<label class="form-group__label" for="name">Confirm New Password</label>

0 commit comments

Comments
 (0)