Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Password complexity #97

Merged
merged 5 commits into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 (
<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;