mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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:
		
							parent
							
								
									38cfc549f2
								
							
						
					
					
						commit
						566d0613a4
					
				@ -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"
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user