1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

refactor: improve password error handling (#1118)

* refactor: improve password error handling

* Update src/component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker.tsx

Co-authored-by: Thomas Heartman <thomas@getunleash.ai>

Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
This commit is contained in:
olav 2022-06-28 09:47:22 +02:00 committed by GitHub
parent 38cfc549f2
commit 566d0613a4
3 changed files with 36 additions and 50 deletions

View File

@ -1,13 +1,14 @@
import React, { useState } from 'react';
import classnames from 'classnames';
import { Avatar, TextField, Typography, Alert } from '@mui/material';
import { Avatar, TextField, Typography } from '@mui/material';
import { trim } from 'component/common/util';
import { modalStyles } from 'component/admin/users/util';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import PasswordChecker from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
import PasswordChecker, {
PASSWORD_FORMAT_MESSAGE,
} from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
import { useThemeStyles } from 'themes/themeStyles';
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IUser } from 'interfaces/user';
interface IChangePasswordProps {
@ -24,30 +25,25 @@ const ChangePassword = ({
user,
}: IChangePasswordProps) => {
const [data, setData] = useState<Record<string, string>>({});
const [error, setError] = useState<Record<string, string>>({});
const [error, setError] = useState<string>();
const [validPassword, setValidPassword] = useState(false);
const { classes: themeStyles } = useThemeStyles();
const updateField: React.ChangeEventHandler<HTMLInputElement> = event => {
setError({});
setError(undefined);
setData({ ...data, [event.target.name]: trim(event.target.value) });
};
const submit = async (event: React.SyntheticEvent) => {
event.preventDefault();
if (data.password !== data.confirm) {
return;
}
if (!validPassword) {
if (!data.password || data.password.length < 8) {
setError({
password:
'You must specify a password with at least 8 chars.',
});
return;
}
if (!(data.password === data.confirm)) {
setError({ confirm: 'Passwords does not match' });
return;
}
setError(PASSWORD_FORMAT_MESSAGE);
return;
}
try {
@ -55,16 +51,15 @@ const ChangePassword = ({
setData({});
closeDialog();
} catch (error: unknown) {
const msg =
(error instanceof Error && error.message) ||
'Could not update password';
setError({ general: msg });
console.warn(error);
setError(PASSWORD_FORMAT_MESSAGE);
}
};
const onCancel = (event: React.SyntheticEvent) => {
event.preventDefault();
setData({});
setError(undefined);
closeDialog();
};
@ -77,6 +72,7 @@ const ChangePassword = ({
primaryButtonText="Save"
title="Update password"
secondaryButtonText="Cancel"
maxWidth="xs"
>
<form
onSubmit={submit}
@ -85,10 +81,6 @@ const ChangePassword = ({
themeStyles.flexColumn
)}
>
<ConditionallyRender
condition={Boolean(error.general)}
show={<Alert severity="error">{error.general}</Alert>}
/>
<Typography variant="subtitle1">
Changing password for user
</Typography>
@ -117,7 +109,8 @@ const ChangePassword = ({
name="password"
type="password"
value={data.password}
helperText={error.password}
error={Boolean(error)}
helperText={error}
onChange={updateField}
variant="outlined"
size="small"
@ -127,8 +120,6 @@ const ChangePassword = ({
name="confirm"
type="password"
value={data.confirm}
error={error.confirm !== undefined}
helperText={error.confirm}
onChange={updateField}
variant="outlined"
size="small"

View File

@ -3,10 +3,10 @@ import { Button, Typography } from '@mui/material';
import classnames from 'classnames';
import { useStyles } from './EditProfile.styles';
import { useThemeStyles } from 'themes/themeStyles';
import PasswordChecker from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
import PasswordChecker, {
PASSWORD_FORMAT_MESSAGE,
} from 'component/user/common/ResetPasswordForm/PasswordChecker/PasswordChecker';
import PasswordMatcher from 'component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher';
import { Alert } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useLoading from 'hooks/useLoading';
import {
BAD_REQUEST,
@ -17,6 +17,7 @@ import {
import { formatApiPath } from 'utils/formatPath';
import PasswordField from 'component/common/PasswordField/PasswordField';
import { headers } from 'utils/apiUtils';
import { formatUnknownError } from 'utils/formatUnknownError';
interface IEditProfileProps {
setEditingProfile: React.Dispatch<React.SetStateAction<boolean>>;
@ -31,7 +32,7 @@ const EditProfile = ({
const { classes: themeStyles } = useThemeStyles();
const [loading, setLoading] = useState(false);
const [validPassword, setValidPassword] = useState(false);
const [error, setError] = useState('');
const [error, setError] = useState<string>();
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const ref = useLoading(loading);
@ -39,13 +40,13 @@ const EditProfile = ({
const submit = async (e: SyntheticEvent) => {
e.preventDefault();
if (!validPassword || password !== confirmPassword) {
setError(
'Password is not valid, or your passwords do not match. Please provide a password with length over 10 characters, an uppercase letter, a lowercase letter, a number and a symbol.'
);
if (password !== confirmPassword) {
return;
} else if (!validPassword) {
setError(PASSWORD_FORMAT_MESSAGE);
} else {
setLoading(true);
setError('');
setError(undefined);
try {
const path = formatApiPath('api/admin/user/change-password');
const res = await fetch(path, {
@ -55,8 +56,8 @@ const EditProfile = ({
credentials: 'include',
});
handleResponse(res);
} catch (e: any) {
setError(e);
} catch (error: unknown) {
setError(formatUnknownError(error));
}
}
setLoading(false);
@ -64,9 +65,7 @@ const EditProfile = ({
const handleResponse = (res: Response) => {
if (res.status === BAD_REQUEST) {
setError(
'Password could not be accepted. Please make sure you are inputting a valid password.'
);
setError(PASSWORD_FORMAT_MESSAGE);
}
if (res.status === UNAUTHORIZED) {
@ -94,14 +93,6 @@ const EditProfile = ({
>
Update password
</Typography>
<ConditionallyRender
condition={Boolean(error)}
show={
<Alert data-loading severity="error">
{error}
</Alert>
}
/>
<form
className={classnames(styles.form, themeStyles.contentSpacingY)}
>
@ -115,6 +106,8 @@ const EditProfile = ({
label="Password"
name="password"
value={password}
error={Boolean(error)}
helperText={error}
autoComplete="new-password"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setPassword(e.target.value)

View File

@ -35,6 +35,8 @@ const LOWERCASE_ERROR =
'The password must contain at least one lowercase letter.';
const REPEATING_CHARACTER_ERROR =
'The password may not contain sequences of three or more repeated characters.';
export const PASSWORD_FORMAT_MESSAGE =
'The password must be at least 10 characters long and must include an uppercase letter, a lowercase letter, a number, and a symbol.';
const PasswordChecker = ({
password,
@ -159,7 +161,7 @@ const PasswordChecker = ({
<>
<Typography variant="body2" className={styles.title} data-loading>
Please set a strong password
<HelpIcon tooltip="Your password needs to be at least ten characters long, and include an uppercase letter, a lowercase letter, a number and a symbol to be a valid OWASP password" />
<HelpIcon tooltip={PASSWORD_FORMAT_MESSAGE} />
</Typography>
<div
className={styles.container}