Skip to content

Commit

Permalink
Password complexity (#97)
Browse files Browse the repository at this point in the history
* added password complexity indicator to register

* added password complexity indicator to change password
  • Loading branch information
luwol03 authored Jan 28, 2022
1 parent ff5e367 commit 7e4771d
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useEffect } from "react";

import { bytesLength } from "../../../utils/index.js";

import "./PasswordComplexityIndicator.scss";

const PasswordComplexityIndicator = ({
password,
complexity,
setComplexity,
}) => {
useEffect(() => {
const passwordLength = bytesLength(password);

const length = passwordLength >= 8 && passwordLength <= 72;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasNonAlphas = /\W/.test(password);

if (length) {
setComplexity(
length + hasUpperCase + hasLowerCase + hasNumbers + hasNonAlphas
);
} else if (passwordLength !== 0) {
setComplexity(1);
} else {
setComplexity(0);
}
}, [password, setComplexity]);

return (
<div className="password-complexity-indicator">
<div
style={{ width: `${(100 / 5) * complexity}%` }}
className={`bar complexity-${complexity}`}
></div>
</div>
);
};

export default PasswordComplexityIndicator;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@import "../../../colors";

.password-complexity-indicator {
margin-top: 12px;
width: 100%;
height: 6px;
border-radius: 5px;
background-color: #eee;

.bar {
border-radius: 3px;
transition: all 0.25s ease-in;
width: 0%;
height: 6px;

&.complexity-0,
&.complexity-1,
&.complexity-2 {
background-color: $color-red;
}

&.complexity-3 {
background-color: $color-yellow;
}

&.complexity-4,
&.complexity-5 {
background-color: $color-green;
}
}
}
2 changes: 2 additions & 0 deletions src/colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ $color-grey: #cbcbcb;
$color-red-light: #f9c5cc;
$color-red: #ff586e;
$color-red-dark: #fd334e;
$color-yellow: #ffeb3b;
$color-yellow-dark: #c8b900;
$color-green-light: #c8f1e5;
$color-green: #0acf97;
$color-green-dark: #06b483;
1 change: 1 addition & 0 deletions src/i18n/locales/en/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@
"emailInUse": "The email is already used",
"emailNotValid": "Not a valid email address",
"passwordsDontMatch": "The passwords are not the same",
"passwordsNotComplex": "Password not complex enough",
"alreadyHaveAccount": "Already have an account?",
"readPrivacy": "I have read the [Privacy Policy]",
"acceptTerms": "I accept the [Terms and Conditions]"
Expand Down
3 changes: 0 additions & 3 deletions src/screens/Login/Login.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
@import "../../colors";

.login-form {
max-width: 450px;
max-height: 550px;

margin: auto;
border-radius: 10px;

Expand Down
26 changes: 24 additions & 2 deletions src/screens/Profile/Profile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import PersonIcon from "@material-ui/icons/Person";
import Button from "../../Components/Button/Button.jsx";
import ConfirmDialog from "../../Components/ConfirmDialog/ConfirmDialog.jsx";
import TextInput from "../../Components/Form/TextInput/TextInput.jsx";
import PasswordComplexityIndicator from "../../Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx";
import Modal from "../../Components/Modal/Modal.jsx";
import StatsTable from "../../Components/StatsTable/StatsTable.jsx";

import useSnack from "../../hooks/useSnack.js";
import { signOut } from "../../redux/Actions/login.js";
import { deleteUser, changePassword } from "../../utils/api.js";
import { bytesLength } from "../../utils/index.js";

import "./Profile.scss";

Expand All @@ -32,6 +34,8 @@ const Profile = () => {

const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [passwordComplexity, setPasswordComplexity] = useState(0);
const [isPasswordComplexityOk, setIsPasswordComplexityOk] = useState(false);
const [repeatedNewPassword, setRepeatedNewPassword] = useState("");
const [canSubmitPasswordChange, setCanSubmitPasswordChange] = useState(false);

Expand All @@ -47,6 +51,8 @@ const Profile = () => {
setOldPassword("");
setNewPassword("");
setRepeatedNewPassword("");
setPasswordComplexity(0);
setIsPasswordComplexityOk(false);
}, []);

const openEmailModal = useCallback(() => {
Expand Down Expand Up @@ -93,15 +99,24 @@ const Profile = () => {
});
}, [dispatch]);

useEffect(() => {
const passwordLength = bytesLength(newPassword);

setIsPasswordComplexityOk(
passwordLength >= 8 && passwordLength <= 72 && passwordComplexity >= 4
);
}, [newPassword, passwordComplexity]);

useEffect(() => {
setCanSubmitPasswordChange(
newPassword === repeatedNewPassword &&
newPassword !== "" &&
repeatedNewPassword !== "" &&
oldPassword !== ""
oldPassword !== "" &&
isPasswordComplexityOk
);
setNewPasswordError(newPassword !== repeatedNewPassword);
}, [newPassword, oldPassword, repeatedNewPassword]);
}, [isPasswordComplexityOk, newPassword, oldPassword, repeatedNewPassword]);

useEffect(() => {
setCanSubmitDelete(deleteConfirmation === username);
Expand Down Expand Up @@ -193,6 +208,13 @@ const Profile = () => {
setNewPassword(value);
}}
value={newPassword}
error={newPassword !== "" && !isPasswordComplexityOk}
errorText={t("screens.register.passwordsNotComplex")}
/>
<PasswordComplexityIndicator
password={newPassword}
complexity={passwordComplexity}
setComplexity={setPasswordComplexity}
/>
<TextInput
required
Expand Down
28 changes: 26 additions & 2 deletions src/screens/Register/Register.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ArrowBackIcon from "@material-ui/icons/ArrowBack";
import Button from "../../Components/Button/Button.jsx";
import TextInput from "../../Components/Form/TextInput/TextInput.jsx";
import InviteCodeValidIndicator from "../../Components/Indicators/InviteCodeValidIndicator/InviteCodeValidIndicator.jsx";
import PasswordComplexityIndicator from "../../Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx";
import ServerValidIndicator from "../../Components/Indicators/ServerValidIndicator/ServerValidIndicator.jsx";
import UnauthenticatedLayout from "../../Components/Layout/UnauthenticatedLayout/UnauthenticatedLayout.jsx";

Expand All @@ -20,6 +21,7 @@ import {
maxUsernameLength,
pages,
} from "../../utils/constants.js";
import { bytesLength } from "../../utils/index.js";

import "./Register.scss";

Expand All @@ -41,6 +43,8 @@ const Register = ({ image }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [passwordRepeat, setPasswordRepeat] = useState("");
const [passwordComplexity, setPasswordComplexity] = useState(0);
const [isPasswordComplexityOk, setIsPasswordComplexityOk] = useState(false);
const [inviteCode, setInviteCode] = useState("");
const [serverAddressInput, setServerAddressInput] = useState(serverAddress);
const [isSamePassword, setIsSamePassword] = useState(true);
Expand Down Expand Up @@ -154,6 +158,14 @@ const Register = ({ image }) => {
]
);

useEffect(() => {
const passwordLength = bytesLength(password);

setIsPasswordComplexityOk(
passwordLength >= 8 && passwordLength <= 72 && passwordComplexity >= 4
);
}, [password, passwordComplexity]);

useEffect(() => {
setIsSamePassword(password === passwordRepeat);
}, [password, passwordRepeat]);
Expand Down Expand Up @@ -188,6 +200,7 @@ const Register = ({ image }) => {
!email ||
!password ||
!passwordRepeat ||
!isPasswordComplexityOk ||
!isServerValid ||
!isSamePassword ||
!isEmailValid ||
Expand All @@ -214,6 +227,7 @@ const Register = ({ image }) => {
acceptTerms,
isPrivacyPolicyValid,
isTermsAndConditionsValid,
isPasswordComplexityOk,
]);

useEffect(() => {
Expand Down Expand Up @@ -298,12 +312,22 @@ const Register = ({ image }) => {
}}
value={password}
error={
!isSamePassword && password !== "" && passwordRepeat !== ""
(!isSamePassword && password !== "" && passwordRepeat !== "") ||
!isPasswordComplexityOk
}
errorText={
!isPasswordComplexityOk
? t("screens.register.passwordsNotComplex")
: t("screens.register.passwordsDontMatch")
}
errorText={t("screens.register.passwordsDontMatch")}
maxLength={maxTextfieldLength}
minLength={8}
/>
<PasswordComplexityIndicator
password={password}
complexity={passwordComplexity}
setComplexity={setPasswordComplexity}
/>
<TextInput
required
type="password"
Expand Down
3 changes: 0 additions & 3 deletions src/screens/Register/Register.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
@import "../../colors";

.register-form {
max-height: 650px;
max-width: 450px;

margin: auto;
border-radius: 10px;

Expand Down
8 changes: 8 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,11 @@ export const dayDateDiff = (date1, date2) => {

return Math.floor(timeDiff / (1000 * 60 * 60 * 24));
};

/**
* Return the length of a string in bytes
* See: https://stackoverflow.com/questions/5515869/string-length-in-bytes-in-javascript
* @param {String} string string
* @returns bytesLength
*/
export const bytesLength = (string) => new TextEncoder().encode(string).length;

0 comments on commit 7e4771d

Please sign in to comment.