From 3de107e94706c3351158fd4c79eee8dee85c74b2 Mon Sep 17 00:00:00 2001 From: luwol03 Date: Sun, 23 Jan 2022 20:10:11 +0100 Subject: [PATCH 1/4] added passsword complexity indicator --- .../PasswordComplexityIndicator.jsx | 41 +++++++++++++++++++ .../PasswordComplexityIndicator.scss | 29 +++++++++++++ src/colors.scss | 2 + src/i18n/locales/en/default.json | 1 + src/screens/Login/Login.scss | 3 -- src/screens/Register/Register.jsx | 28 ++++++++++++- src/screens/Register/Register.scss | 3 -- src/utils/index.js | 8 ++++ 8 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx create mode 100644 src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss diff --git a/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx new file mode 100644 index 00000000..5277073a --- /dev/null +++ b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx @@ -0,0 +1,41 @@ +import React, { useEffect } from "react"; + +import { bytesLength } from "../../../utils/index.js"; + +import "./PasswordComplexityIndicator.scss"; + +const PasswordComplexityIndicator = ({ + password, + complexity, + setComplexity, + className, +}) => { + 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); + + setComplexity( + length + ? length + hasUpperCase + hasLowerCase + hasNumbers + hasNonAlphas + : passwordLength !== 0 + ? 1 + : 0 + ); + }, [password, setComplexity]); + + return ( +
+
+
+ ); +}; + +export default PasswordComplexityIndicator; diff --git a/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss new file mode 100644 index 00000000..14b4d8fc --- /dev/null +++ b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss @@ -0,0 +1,29 @@ +@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; + } + } +} diff --git a/src/colors.scss b/src/colors.scss index 93fe90ec..2b5ec40e 100644 --- a/src/colors.scss +++ b/src/colors.scss @@ -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; diff --git a/src/i18n/locales/en/default.json b/src/i18n/locales/en/default.json index e37ae53b..4e701f73 100644 --- a/src/i18n/locales/en/default.json +++ b/src/i18n/locales/en/default.json @@ -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]" diff --git a/src/screens/Login/Login.scss b/src/screens/Login/Login.scss index 6274071e..92dd3134 100644 --- a/src/screens/Login/Login.scss +++ b/src/screens/Login/Login.scss @@ -1,9 +1,6 @@ @import "../../colors"; .login-form { - max-width: 450px; - max-height: 550px; - margin: auto; border-radius: 10px; diff --git a/src/screens/Register/Register.jsx b/src/screens/Register/Register.jsx index 9bb6963d..5047b3f5 100644 --- a/src/screens/Register/Register.jsx +++ b/src/screens/Register/Register.jsx @@ -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"; @@ -20,6 +21,7 @@ import { maxUsernameLength, pages, } from "../../utils/constants.js"; +import { bytesLength } from "../../utils/index.js"; import "./Register.scss"; @@ -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); @@ -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]); @@ -188,6 +200,7 @@ const Register = ({ image }) => { !email || !password || !passwordRepeat || + !isPasswordComplexityOk || !isServerValid || !isSamePassword || !isEmailValid || @@ -214,6 +227,7 @@ const Register = ({ image }) => { acceptTerms, isPrivacyPolicyValid, isTermsAndConditionsValid, + isPasswordComplexityOk, ]); useEffect(() => { @@ -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} /> + { 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; From 05856bc1e5ed69a158b3e9e67b702f02c70a342f Mon Sep 17 00:00:00 2001 From: luwol03 Date: Sun, 23 Jan 2022 20:22:33 +0100 Subject: [PATCH 2/4] added password complexity indicator to change password --- src/screens/Profile/Profile.jsx | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/screens/Profile/Profile.jsx b/src/screens/Profile/Profile.jsx index f4973c73..9cb837dc 100644 --- a/src/screens/Profile/Profile.jsx +++ b/src/screens/Profile/Profile.jsx @@ -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"; @@ -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); @@ -47,6 +51,8 @@ const Profile = () => { setOldPassword(""); setNewPassword(""); setRepeatedNewPassword(""); + setPasswordComplexity(0); + setIsPasswordComplexityOk(false); }, []); const openEmailModal = useCallback(() => { @@ -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); @@ -193,6 +208,13 @@ const Profile = () => { setNewPassword(value); }} value={newPassword} + error={newPassword !== "" && !isPasswordComplexityOk} + errorText={t("screens.register.passwordsNotComplex")} + /> + Date: Sun, 23 Jan 2022 20:35:34 +0100 Subject: [PATCH 3/4] lint: style --- .../PasswordComplexityIndicator.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss index 14b4d8fc..6b792721 100644 --- a/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss +++ b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.scss @@ -18,9 +18,11 @@ &.complexity-2 { background-color: $color-red; } + &.complexity-3 { background-color: $color-yellow; } + &.complexity-4, &.complexity-5 { background-color: $color-green; From 09541a1c737d930d2ebc578b50e1b90a7f942887 Mon Sep 17 00:00:00 2001 From: luwol03 Date: Fri, 28 Jan 2022 08:40:47 +0100 Subject: [PATCH 4/4] improved set complexity check --- .../PasswordComplexityIndicator.jsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx index 5277073a..c1800bcf 100644 --- a/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx +++ b/src/Components/Indicators/PasswordComplexityIndicator/PasswordComplexityIndicator.jsx @@ -8,7 +8,6 @@ const PasswordComplexityIndicator = ({ password, complexity, setComplexity, - className, }) => { useEffect(() => { const passwordLength = bytesLength(password); @@ -19,13 +18,15 @@ const PasswordComplexityIndicator = ({ const hasNumbers = /\d/.test(password); const hasNonAlphas = /\W/.test(password); - setComplexity( - length - ? length + hasUpperCase + hasLowerCase + hasNumbers + hasNonAlphas - : passwordLength !== 0 - ? 1 - : 0 - ); + if (length) { + setComplexity( + length + hasUpperCase + hasLowerCase + hasNumbers + hasNonAlphas + ); + } else if (passwordLength !== 0) { + setComplexity(1); + } else { + setComplexity(0); + } }, [password, setComplexity]); return (