Skip to content

Commit

Permalink
feat(web): add password policy visible feedback on password change (#…
Browse files Browse the repository at this point in the history
…5714)

This makes user feedback regarding the password policy more prominent.

Closes #5048

Signed-off-by: mind-ar
  • Loading branch information
mind-ar committed Aug 3, 2023
1 parent 672da1d commit b0746dc
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 7 deletions.
2 changes: 1 addition & 1 deletion internal/suites/Standalone/configuration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ password_policy:
## zxcvbn: uses zxcvbn for password strength checking (see: https://github.com/dropbox/zxcvbn)
## Note that the zxcvbn option does not prohibit the user from using a weak password,
## it only offers feedback about the strength of the password they are entering.
## if you need to enforce password rules, you should use `mode=classic`
## if you need to enforce password rules, you should use the standard password policy
enabled: false
...
68 changes: 66 additions & 2 deletions web/src/components/PasswordMeter.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
// import React from "react";

import { render } from "@testing-library/react";
import { render, screen } from "@testing-library/react";

import PasswordMeter from "@components/PasswordMeter";
import "@i18n/index.ts";
Expand Down Expand Up @@ -41,3 +41,67 @@ it("renders adjusted height without crashing", () => {
/>,
);
});

it("displays warning message on password too large", async () => {
const maxLenght = 5;
render(
<PasswordMeter
value={"password"}
policy={{
max_length: maxLenght,
min_length: 4,
min_score: 0,
require_lowercase: true,
require_number: true,
require_special: true,
require_uppercase: true,
mode: PasswordPolicyMode.Standard,
}}
/>,
);

const text = `Must not be more than ${maxLenght} characters in length`;
expect(screen.queryByText(text)).toBeInTheDocument();
});

it("displays warning message on password too short", async () => {
const minLenght = 5;
render(
<PasswordMeter
value={"abc"}
policy={{
max_length: 0,
min_length: minLenght,
min_score: 0,
require_lowercase: true,
require_number: true,
require_special: true,
require_uppercase: true,
mode: PasswordPolicyMode.Standard,
}}
/>,
);

const text = `Must be at least ${minLenght} characters in length`;
expect(screen.queryByText(text)).toBeInTheDocument();
});

it("displays warning message on password policy fail", async () => {
render(
<PasswordMeter
value={""}
policy={{
max_length: 0,
min_length: 0,
min_score: 0,
require_lowercase: true,
require_number: true,
require_special: true,
require_uppercase: true,
mode: PasswordPolicyMode.Standard,
}}
/>,
);

expect(screen.queryByText("The password does not meet the password policy")).toBeInTheDocument();
});
33 changes: 29 additions & 4 deletions web/src/components/PasswordMeter.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";

import { Box, Theme } from "@mui/material";
import { Alert, AlertTitle, Box, Theme } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
import classnames from "classnames";
import { useTranslation } from "react-i18next";
Expand All @@ -20,9 +20,12 @@ const PasswordMeter = function (props: Props) {
const [passwordScore, setPasswordScore] = useState(0);
const [maxScores, setMaxScores] = useState(0);
const [feedback, setFeedback] = useState("");
const [feedbackTitle, setFeedbackTitle] = useState("");

useEffect(() => {
const password = props.value;
setFeedback("");
setFeedbackTitle("");
if (props.policy.mode === PasswordPolicyMode.Standard) {
//use mode mode
setMaxScores(4);
Expand Down Expand Up @@ -87,14 +90,16 @@ const PasswordMeter = function (props: Props) {
score += hits > 0 ? 1 : 0;
score += required === hits ? 1 : 0;
if (warning !== "") {
setFeedback(translate("The password does not meet the password policy") + ":\n" + warning);
setFeedbackTitle(translate("The password does not meet the password policy"));
}
setFeedback(translate(warning));

setPasswordScore(score);
} else if (props.policy.mode === PasswordPolicyMode.ZXCVBN) {
//use zxcvbn mode
setMaxScores(5);
const { score, feedback } = zxcvbn(password);
setFeedback(feedback.warning);
setFeedbackTitle(feedback.warning);
setPasswordScore(score);
}
}, [props, translate]);
Expand All @@ -110,11 +115,31 @@ const PasswordMeter = function (props: Props) {
progressContainer: {
width: "100%",
},
feedbackTitle: {
whiteSpace: "break-spaces",
textAlign: "left",
fontSize: "0.85rem",
},
feedback: {
whiteSpace: "break-spaces",
textAlign: "left",
fontSize: "0.7rem",
},
}))();

return (
<Box className={styles.progressContainer}>
<Box title={feedback} className={classnames(styles.progressBar)} />
<Box className={classnames(styles.progressBar)} />
{(feedbackTitle !== "" || feedback !== "") && (
<Alert severity="warning">
{feedbackTitle !== "" && (
<AlertTitle className={classnames(styles.feedbackTitle)}>
<p>{feedbackTitle}</p>
</AlertTitle>
)}
<p className={classnames(styles.feedback)}>{feedback}</p>
</Alert>
)}
</Box>
);
};
Expand Down

0 comments on commit b0746dc

Please sign in to comment.