Skip to content

Commit

Permalink
fix: added username and password validation for various screen (#4650)
Browse files Browse the repository at this point in the history
* fix: added username and password validation for various screens

Signed-off-by: Sahil <sahil.kumar@harness.io>

* fix: fixed tests

Signed-off-by: Sahil <sahil.kumar@harness.io>

---------

Signed-off-by: Sahil <sahil.kumar@harness.io>
Co-authored-by: Saranya Jena <saranya.jena@harness.io>
  • Loading branch information
SahilKr24 and Saranya-jena committed May 18, 2024
1 parent c4d23a7 commit ae21ffb
Show file tree
Hide file tree
Showing 13 changed files with 78 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
position: relative;
}
.eyeIcon {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
z-index: 1;
margin-top: 5px;
margin-right: 5px;
}
}
10 changes: 5 additions & 5 deletions chaoscenter/web/src/components/PasswordInput/PasswordInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { FormInput, Layout, Text } from '@harnessio/uicore';
import { Color, FontVariation } from '@harnessio/design-system';
import style from './PasswordInput.module.scss';

interface PassowrdInputProps {
interface PasswordInputProps {
disabled?: boolean;
placeholder?: string;
name: string;
label: string;
}

const PassowrdInput = (props: PassowrdInputProps): React.ReactElement => {
const PasswordInput = (props: PasswordInputProps): React.ReactElement => {
const { disabled, label, name, placeholder } = props;
const [showPassword, setShowPassword] = React.useState(false);
const stateIcon: IconName = showPassword ? 'eye-off' : 'eye-open';
Expand All @@ -31,15 +31,15 @@ const PassowrdInput = (props: PassowrdInputProps): React.ReactElement => {
<FormInput.Text
name={name}
inputGroup={{
type: showPassword ? 'text' : 'password'
type: showPassword ? 'text' : 'password',
rightElement: <Icon name={stateIcon} size={20} onClick={handleToggleClick} className={style.eyeIcon} />
}}
placeholder={placeholder}
disabled={disabled}
/>
<Icon name={stateIcon} size={20} onClick={handleToggleClick} className={style.eyeIcon} />
</div>
</Layout.Vertical>
);
};

export default PassowrdInput;
export default PasswordInput;
4 changes: 2 additions & 2 deletions chaoscenter/web/src/components/PasswordInput/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import PassowrdInput from './PasswordInput';
import PasswordInput from './PasswordInput';

export default PassowrdInput;
export default PasswordInput;
12 changes: 12 additions & 0 deletions chaoscenter/web/src/constants/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ^[a-zA-Z] # Must start with a letter
// [a-zA-Z0-9_-] # Allow letters, digits, underscores, and hyphens
// {2,15}$ # Ensure the length of the username is between 3 and 16 characters (1 character is already matched above)
export const USERNAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]{2,15}$/;

// ^(?=.*[a-z]) # At least one lowercase letter
// (?=.*[A-Z]) # At least one uppercase letter
// (?=.*\d) # At least one digit
// (?=.*[@$!%*?_&]) # At least one special character @$!%*?_&
// [A-Za-z\d@$!%*?_&] # Allowed characters: letters, digits, special characters @$!%*?_&
// {8,16}$ # Length between 8 to 16 characters
export const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?_&])[A-Za-z\d@$!%*?_&]{8,16}$/;
8 changes: 7 additions & 1 deletion chaoscenter/web/src/strings/strings.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,8 @@ featureRestriction:
resourceOveruseEnterprise: >-
You have exceeded your resource subscription limit. Consider increasing your
limits to keep shipping.
fieldMaxLength: Length should be {{length}} characters maximum.
fieldMinLength: Length should be {{length}} characters minimum.
fieldSelector: Field Selector
fixAllErrors: Fix all the errors indicated in the YAML Editor to resolve the issue.
generateSSH: Generate New SSH Key
Expand Down Expand Up @@ -601,7 +603,7 @@ nameIdDescriptionTags:
tagsLabel: Tags
nameIsARequiredField: Name is a required field
nameIsRequired: Name is a required in order to update.
nameVaidText: Name can only contain alphabets.
nameValidText: Name can only contain alphabets.
namespace: Namespace
namespaceScopeDescription: Allows LitmusChaos to perform and monitor chaos in a namespace.
namespaceWide: Namespace-wide
Expand Down Expand Up @@ -738,6 +740,9 @@ passedRuns: Passed Runs
password: Password
passwordIsRequired: Password is a required field
passwordResetSuccess: Password reset successfully
passwordValidation: >-
Password must contain at least one uppercase letter, one lowercase letter, one
number and one special character
passwordsDoNotMatch: Passwords do not match
pauseRun: Pause Run
pending: Pending
Expand Down Expand Up @@ -1132,6 +1137,7 @@ userCreatedOn: User Created On
userManagement: User Management
username: Username
usernameIsRequired: Username is a required field
usernameValidText: Username can only contain letters, digits, underscores, and hyphens
usersNotAvailableMessage: No users available to send invitation
usersNotAvailableTitle: No users available
validationError: Validation Error
Expand Down
6 changes: 5 additions & 1 deletion chaoscenter/web/src/strings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ export interface StringsMap {
'featureRestriction.resourceLevelUpFree': PrimitiveObject<'resourceLimit' | 'resourceName'>
'featureRestriction.resourceLevelUpTeam': PrimitiveObject<'resourceLimit'>
'featureRestriction.resourceOveruseEnterprise': unknown
'fieldMaxLength': PrimitiveObject<'length'>
'fieldMinLength': PrimitiveObject<'length'>
'fieldSelector': unknown
'fixAllErrors': unknown
'generateSSH': unknown
Expand Down Expand Up @@ -504,7 +506,7 @@ export interface StringsMap {
'nameIdDescriptionTags.tagsLabel': unknown
'nameIsARequiredField': unknown
'nameIsRequired': unknown
'nameVaidText': unknown
'nameValidText': unknown
'namespace': unknown
'namespaceScopeDescription': unknown
'namespaceWide': unknown
Expand Down Expand Up @@ -614,6 +616,7 @@ export interface StringsMap {
'password': unknown
'passwordIsRequired': unknown
'passwordResetSuccess': unknown
'passwordValidation': unknown
'passwordsDoNotMatch': unknown
'pauseRun': unknown
'pending': unknown
Expand Down Expand Up @@ -961,6 +964,7 @@ export interface StringsMap {
'userManagement': unknown
'username': unknown
'usernameIsRequired': unknown
'usernameValidText': unknown
'usersNotAvailableMessage': unknown
'usersNotAvailableTitle': unknown
'validationError': unknown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default function AccountDetailsChangeView(props: AccountDetailsChangeView
validationSchema={Yup.object().shape({
name: Yup.string()
.trim()
.matches(/^[a-zA-Z ]*$/, getString('nameVaidText'))
.matches(/^[a-zA-Z ]*$/, getString('nameValidText'))
.required(getString('nameIsRequired')),
email: Yup.string().trim().email(getString('invalidEmailText')).required(getString('emailIsRequired'))
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as Yup from 'yup';
import type { UseMutateFunction } from '@tanstack/react-query';
import { useStrings } from '@strings';
import type { UpdatePasswordMutationProps, UpdatePasswordOkResponse } from '@api/auth';
import { PASSWORD_REGEX } from '@constants/validation';

interface AccountPasswordChangeViewProps {
handleClose: () => void;
Expand Down Expand Up @@ -79,8 +80,14 @@ export default function AccountPasswordChangeView(props: AccountPasswordChangeVi
onSubmit={values => handleSubmit(values)}
validationSchema={Yup.object().shape({
oldPassword: Yup.string().required(getString('enterOldPassword')),
newPassword: Yup.string().required(getString('enterNewPassword')),
reEnterNewPassword: Yup.string().required(getString('reEnterNewPassword'))
newPassword: Yup.string()
.required(getString('enterNewPassword'))
.min(8, getString('fieldMinLength', { length: 8 }))
.max(16, getString('fieldMaxLength', { length: 16 }))
.matches(PASSWORD_REGEX, getString('passwordValidation')),
reEnterNewPassword: Yup.string()
.required(getString('reEnterNewPassword'))
.oneOf([Yup.ref('newPassword'), null], getString('passwordsDoNotMatch'))
})}
>
{formikProps => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ describe('AccountPasswordChangeView Tests', () => {
const submitButton = screen.getByText('confirm');

fireEvent.change(screen.getByPlaceholderText('oldPassword'), { target: { value: 'oldPass' } });
fireEvent.change(screen.getByPlaceholderText('newPassword'), { target: { value: 'newPass' } });
fireEvent.change(screen.getByPlaceholderText('reEnterNewPassword'), { target: { value: 'newPass' } });
fireEvent.change(screen.getByPlaceholderText('newPassword'), { target: { value: 'Password@123' } });
fireEvent.change(screen.getByPlaceholderText('reEnterNewPassword'), { target: { value: 'Password@123' } });

await waitFor(() => expect(submitButton).not.toBeDisabled());
});

test('handles submit with correct data', async () => {
renderComponent();
fireEvent.change(screen.getByPlaceholderText('oldPassword'), { target: { value: 'oldPass' } });
fireEvent.change(screen.getByPlaceholderText('newPassword'), { target: { value: 'newPass' } });
fireEvent.change(screen.getByPlaceholderText('reEnterNewPassword'), { target: { value: 'newPass' } });
fireEvent.change(screen.getByPlaceholderText('newPassword'), { target: { value: 'Password@123' } });
fireEvent.change(screen.getByPlaceholderText('reEnterNewPassword'), { target: { value: 'Password@123' } });
fireEvent.click(screen.getByText('confirm'));

await waitFor(() => {
Expand All @@ -57,7 +57,7 @@ describe('AccountPasswordChangeView Tests', () => {
body: {
username: 'testuser',
oldPassword: 'oldPass',
newPassword: 'newPass'
newPassword: 'Password@123'
}
},
expect.anything()
Expand Down
15 changes: 12 additions & 3 deletions chaoscenter/web/src/views/CreateNewUser/CreateNewUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Icon } from '@harnessio/icons';
import * as Yup from 'yup';
import type { CreateUserMutationProps, User } from '@api/auth';
import { useStrings } from '@strings';
import { PASSWORD_REGEX, USERNAME_REGEX } from '@constants/validation';

interface CreateNewUserViewProps {
createNewUserMutation: UseMutateFunction<User, unknown, CreateUserMutationProps<never>, unknown>;
Expand Down Expand Up @@ -62,10 +63,18 @@ export default function CreateNewUserView(props: CreateNewUserViewProps): React.
}}
onSubmit={values => handleSubmit(values)}
validationSchema={Yup.object().shape({
name: Yup.string(),
name: Yup.string().max(32),
email: Yup.string().email(getString('invalidEmailText')).required(getString('emailIsRequired')),
username: Yup.string().required(getString('usernameIsRequired')),
password: Yup.string().required(getString('passwordIsRequired')),
username: Yup.string()
.required(getString('usernameIsRequired'))
.min(3, getString('fieldMinLength', { length: 3 }))
.max(16, getString('fieldMaxLength', { length: 16 }))
.matches(USERNAME_REGEX, getString('usernameValidText')),
password: Yup.string()
.required(getString('passwordIsRequired'))
.min(8, getString('fieldMinLength', { length: 8 }))
.max(16, getString('fieldMaxLength', { length: 16 }))
.matches(PASSWORD_REGEX, getString('passwordValidation')),
reEnterPassword: Yup.string()
.required(getString('reEnterPassword'))
.oneOf([Yup.ref('password'), null], getString('passwordsDoNotMatch'))
Expand Down
4 changes: 2 additions & 2 deletions chaoscenter/web/src/views/Gitops/Gitops.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type {
import { useDocumentTitle, useRouteWithBaseUrl } from '@hooks';
import { useStrings } from '@strings';
import Loader from '@components/Loader';
import PassowrdInput from '@components/PasswordInput';
import PasswordInput from '@components/PasswordInput';
import css from './Gitops.module.scss';

interface GitopsData {
Expand Down Expand Up @@ -255,7 +255,7 @@ export default function GitopsView({
]}
/>
{formikProps.values.authType === AuthType.TOKEN && (
<PassowrdInput
<PasswordInput
name="token"
label={getString('accessToken')}
placeholder={getString('accessTokenPlaceholder')}
Expand Down
9 changes: 7 additions & 2 deletions chaoscenter/web/src/views/Login/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { Formik, Button, Text, Container, Layout } from '@harnessio/uicore';
import { Icon } from '@harnessio/icons';
import { Color } from '@harnessio/design-system';
import { Form } from 'formik';
import * as Yup from 'yup';
import type { UseMutateFunction } from '@tanstack/react-query';
import AuthLayout from '@components/AuthLayout/AuthLayout';
import { useStrings } from '@strings';
import type { ErrorModel, LoginMutationProps, LoginResponse, GetCapabilitiesOkResponse } from '@api/auth';
import PassowrdInput from '@components/PasswordInput';
import PasswordInput from '@components/PasswordInput';
import UserNameInput from '@components/UserNameInput';

interface LoginForm {
Expand Down Expand Up @@ -38,6 +39,10 @@ export const LoginPageView = ({ handleLogin, loading, capabilities }: LoginPageV
<Formik<LoginForm>
initialValues={{ username: '', password: '' }}
formName="loginPageForm"
validationSchema={Yup.object().shape({
username: Yup.string().required(getString('isRequired', { field: getString('username') })),
password: Yup.string().required(getString('isRequired', { field: getString('password') }))
})}
onSubmit={data =>
handleLogin({
body: data
Expand All @@ -53,7 +58,7 @@ export const LoginPageView = ({ handleLogin, loading, capabilities }: LoginPageV
placeholder={getString('enterYourUsername')}
disabled={loading}
/>
<PassowrdInput
<PasswordInput
name="password"
label={getString('password')}
placeholder={getString('enterYourPassword')}
Expand Down
11 changes: 9 additions & 2 deletions chaoscenter/web/src/views/ResetPassword/ResetPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Icon } from '@harnessio/icons';
import * as Yup from 'yup';
import type { ResetPasswordOkResponse, ResetPasswordMutationProps } from '@api/auth';
import { useStrings } from '@strings';
import { PASSWORD_REGEX } from '@constants/validation';

interface ResetPasswordViewProps {
handleClose: () => void;
Expand Down Expand Up @@ -76,8 +77,14 @@ export default function ResetPasswordView(props: ResetPasswordViewProps): React.
}}
onSubmit={values => handleSubmit(values)}
validationSchema={Yup.object().shape({
password: Yup.string().required(getString('enterNewPassword')),
reEnterPassword: Yup.string().required(getString('reEnterNewPassword'))
password: Yup.string()
.required(getString('enterNewPassword'))
.min(8, getString('fieldMinLength', { length: 8 }))
.max(16, getString('fieldMaxLength', { length: 16 }))
.matches(PASSWORD_REGEX, getString('passwordValidation')),
reEnterPassword: Yup.string()
.required(getString('reEnterNewPassword'))
.oneOf([Yup.ref('password'), null], getString('passwordsDoNotMatch'))
})}
>
{formikProps => {
Expand Down

0 comments on commit ae21ffb

Please sign in to comment.