Skip to content

Commit

Permalink
Add autocomplete=off to login form fields, password fields (#4107) (#…
Browse files Browse the repository at this point in the history
…4160)

* TextInput - add autoComplete=off to all password fields

No-Issue

* add and use a local copy of patternfly LoginForm

* LoginForm - add autoComplete=off

Issue: AAH-2625

* eslint - remove explicit any

(cherry picked from commit 6dc75bd)
  • Loading branch information
himdel authored Aug 31, 2023
1 parent 00bda29 commit 92afdcc
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGES/2625.bug
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add autocomplete=off to login form fields
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export { ClipboardCopy } from './patternfly-wrappers/clipboard-copy';
export { CompoundFilter } from './patternfly-wrappers/compound-filter';
export { FileUpload } from './patternfly-wrappers/fileupload';
export { LinkTabs } from './patternfly-wrappers/link-tabs';
export { LoginForm } from './patternfly-wrappers/login-form';
export { Main } from './patternfly-wrappers/main';
export { Pagination } from './patternfly-wrappers/pagination';
export { Sort } from './patternfly-wrappers/sort';
Expand Down
200 changes: 200 additions & 0 deletions src/components/patternfly-wrappers/login-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// this comes from @patternfly/[email protected]
// packages/react-core/src/components/LoginPage/LoginForm.tsx
// w/ fixed imports, prettier
// and added autocomplete="off" for username & password
import {
ActionGroup,
Button,
Checkbox,
Form,
FormGroup,
FormHelperText,
InputGroup,
TextInput,
ValidatedOptions,
} from '@patternfly/react-core';
import EyeIcon from '@patternfly/react-icons/dist/esm/icons/eye-icon';
import EyeSlashIcon from '@patternfly/react-icons/dist/esm/icons/eye-slash-icon';
import React from 'react';

export interface LoginFormProps
extends Omit<React.HTMLProps<HTMLFormElement>, 'ref'> {
/** Flag to indicate if the first dropdown item should not gain initial focus */
noAutoFocus?: boolean;
/** Additional classes added to the login main body's form */
className?: string;
/** Flag indicating the helper text is visible * */
showHelperText?: boolean;
/** Content displayed in the helper text component * */
helperText?: React.ReactNode;
/** Icon displayed to the left in the helper text */
helperTextIcon?: React.ReactNode;
/** Label for the username input field */
usernameLabel?: string;
/** Value for the username */
usernameValue?: string;
/** Function that handles the onChange event for the username */
onChangeUsername?: (
value: string,
event: React.FormEvent<HTMLInputElement>,
) => void;
/** Flag indicating if the username is valid */
isValidUsername?: boolean;
/** Label for the password input field */
passwordLabel?: string;
/** Value for the password */
passwordValue?: string;
/** Function that handles the onChange event for the password */
onChangePassword?: (
value: string,
event: React.FormEvent<HTMLInputElement>,
) => void;
/** Flag indicating if the password is valid */
isValidPassword?: boolean;
/** Flag indicating if the user can toggle hiding the password */
isShowPasswordEnabled?: boolean;
/** Accessible label for the show password button */
showPasswordAriaLabel?: string;
/** Accessible label for the hide password button */
hidePasswordAriaLabel?: string;
/** Label for the log in button input */
loginButtonLabel?: string;
/** Flag indicating if the login button is disabled */
isLoginButtonDisabled?: boolean;
/** Function that is called when the login button is clicked */
onLoginButtonClick?: (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => void;
/** Label for the remember me checkbox that indicates the user should be kept logged in. If the label is not provided, the checkbox will not show. */
rememberMeLabel?: string;
/** Flag indicating if the remember me checkbox is checked. */
isRememberMeChecked?: boolean;
/** Function that handles the onChange event for the remember me checkbox */
onChangeRememberMe?: (
checked: boolean,
event: React.FormEvent<HTMLInputElement>,
) => void;
}

export const LoginForm: React.FunctionComponent<LoginFormProps> = ({
noAutoFocus = false,
className = '',
showHelperText = false,
helperText = null,
helperTextIcon = null,
usernameLabel = 'Username',
usernameValue = '',
onChangeUsername = () => undefined,
isValidUsername = true,
passwordLabel = 'Password',
passwordValue = '',
onChangePassword = () => undefined,
isShowPasswordEnabled = false,
hidePasswordAriaLabel = 'Hide password',
showPasswordAriaLabel = 'Show password',
isValidPassword = true,
loginButtonLabel = 'Log In',
isLoginButtonDisabled = false,
onLoginButtonClick = () => undefined,
rememberMeLabel = '',
isRememberMeChecked = false,
onChangeRememberMe = () => undefined,
...props
}: LoginFormProps) => {
const [passwordHidden, setPasswordHidden] = React.useState(true);

const passwordInput = (
<TextInput
isRequired
type={passwordHidden ? 'password' : 'text'}
id='pf-login-password-id'
name='pf-login-password-id'
validated={
isValidPassword ? ValidatedOptions.default : ValidatedOptions.error
}
value={passwordValue}
onChange={onChangePassword}
autoComplete='off'
/>
);

return (
<Form className={className} {...props}>
<FormHelperText
isError={!isValidUsername || !isValidPassword}
isHidden={!showHelperText}
icon={helperTextIcon}
>
{helperText}
</FormHelperText>
<FormGroup
label={usernameLabel}
isRequired
validated={
isValidUsername ? ValidatedOptions.default : ValidatedOptions.error
}
fieldId='pf-login-username-id'
>
<TextInput
autoFocus={!noAutoFocus}
id='pf-login-username-id'
isRequired
validated={
isValidUsername ? ValidatedOptions.default : ValidatedOptions.error
}
type='text'
name='pf-login-username-id'
value={usernameValue}
onChange={onChangeUsername}
autoComplete='off'
/>
</FormGroup>
<FormGroup
label={passwordLabel}
isRequired
validated={
isValidPassword ? ValidatedOptions.default : ValidatedOptions.error
}
fieldId='pf-login-password-id'
>
{isShowPasswordEnabled && (
<InputGroup>
{passwordInput}
<Button
variant='control'
onClick={() => setPasswordHidden(!passwordHidden)}
aria-label={
passwordHidden ? showPasswordAriaLabel : hidePasswordAriaLabel
}
>
{passwordHidden ? <EyeIcon /> : <EyeSlashIcon />}
</Button>
</InputGroup>
)}
{!isShowPasswordEnabled && passwordInput}
</FormGroup>
{rememberMeLabel.length > 0 && (
<FormGroup fieldId='pf-login-remember-me-id'>
<Checkbox
id='pf-login-remember-me-id'
label={rememberMeLabel}
isChecked={isRememberMeChecked}
onChange={onChangeRememberMe}
/>
</FormGroup>
)}
<ActionGroup>
<Button
variant='primary'
type='submit'
onClick={onLoginButtonClick}
isBlock
isDisabled={isLoginButtonDisabled}
>
{loginButtonLabel}
</Button>
</ActionGroup>
</Form>
);
};
LoginForm.displayName = 'LoginForm';
46 changes: 20 additions & 26 deletions src/components/patternfly-wrappers/write-only-field.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { t } from '@lingui/macro';
import { Button, InputGroup, TextInput } from '@patternfly/react-core';
import * as React from 'react';
import React from 'react';

interface IProps {
/** Specify if the value is set on the backend already */
Expand All @@ -13,28 +13,22 @@ interface IProps {
children: React.ReactNode;
}

export class WriteOnlyField extends React.Component<IProps> {
render() {
const { onClear, isValueSet, children } = this.props;

if (!isValueSet) {
return children;
}

return (
<InputGroup>
<TextInput
aria-label={t`hidden value`}
placeholder='••••••••••••••••••••••'
type='password'
isDisabled={isValueSet}
/>
{isValueSet && (
<Button onClick={() => onClear()} variant='control'>
{t`Clear`}
</Button>
)}
</InputGroup>
);
}
}
export const WriteOnlyField = ({ onClear, isValueSet, children }: IProps) =>
!isValueSet ? (
<>{children}</>
) : (
<InputGroup>
<TextInput
aria-label={t`hidden value`}
placeholder='••••••••••••••••••••••'
type='password'
autoComplete='off'
isDisabled={isValueSet}
/>
{isValueSet && (
<Button onClick={() => onClear()} variant='control'>
{t`Clear`}
</Button>
)}
</InputGroup>
);
4 changes: 2 additions & 2 deletions src/components/rbac/user-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Label,
Switch,
TextInput,
TextInputTypes,
Tooltip,
} from '@patternfly/react-core';
import * as React from 'react';
Expand Down Expand Up @@ -90,7 +89,7 @@ export class UserForm extends React.Component<IProps, IState> {
!isReadonly && {
id: 'password',
title: t`Password`,
type: TextInputTypes.password,
type: 'password',
placeholder: isNewUser ? '' : '••••••••••••••••••••••',
formGroupLabelIcon: (
<HelperText
Expand Down Expand Up @@ -124,6 +123,7 @@ export class UserForm extends React.Component<IProps, IState> {
this.setState({ passwordConfirm: value });
}}
type='password'
autoComplete='off'
/>
</FormGroup>
);
Expand Down
3 changes: 3 additions & 0 deletions src/components/repositories/remote-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ export class RemoteForm extends React.Component<IProps, IState> {
validated={this.toError(!('token' in errorMessages))}
isRequired={requiredFields.includes('token')}
type='password'
autoComplete='off'
id='token'
value={remote.token || ''}
onChange={(value) => this.updateRemote(value, 'token')}
Expand Down Expand Up @@ -405,6 +406,7 @@ export class RemoteForm extends React.Component<IProps, IState> {
isDisabled={disabledFields.includes('password')}
id='password'
type='password'
autoComplete='off'
value={remote.password || ''}
onChange={(value) => this.updateRemote(value, 'password')}
/>
Expand Down Expand Up @@ -483,6 +485,7 @@ export class RemoteForm extends React.Component<IProps, IState> {
isDisabled={disabledFields.includes('proxy_password')}
id='proxy_password'
type='password'
autoComplete='off'
value={remote.proxy_password || ''}
onChange={(value) =>
this.updateRemote(value, 'proxy_password')
Expand Down
5 changes: 3 additions & 2 deletions src/components/shared/data-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface IProps {
id: string;
placeholder?: string;
title: string;
type?: TextInputTypes;
type?: string;
}[];
formPrefix?: React.ReactNode;
formSuffix?: React.ReactNode;
Expand Down Expand Up @@ -64,9 +64,10 @@ export class DataForm extends React.Component<IProps> {
id={field.id}
onChange={updateField}
placeholder={field.placeholder}
type={field.type || 'text'}
type={(field.type as TextInputTypes) || 'text'}
validated={validated}
value={model[field.id]}
{...(field.type === 'password' ? { autoComplete: 'off' } : {})}
/>
)}
</FormGroup>
Expand Down
7 changes: 4 additions & 3 deletions src/containers/login/login.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { t } from '@lingui/macro';
import { LoginForm, LoginPage as PFLoginPage } from '@patternfly/react-core';
import { LoginPage as PFLoginPage } from '@patternfly/react-core';
import { ExclamationCircleIcon } from '@patternfly/react-icons';
import * as React from 'react';
import { Redirect, RouteComponentProps, withRouter } from 'react-router-dom';
import Logo from 'src/../static/images/logo_large.svg';
import { ActiveUserAPI } from 'src/api';
import { LoginForm } from 'src/components';
import { AppContext } from 'src/loaders/app-context';
import { Paths } from 'src/paths';
import { ParamHelper } from 'src/utilities/';
Expand Down Expand Up @@ -70,8 +71,8 @@ class LoginPage extends React.Component<RouteComponentProps, IState> {
);
}

private handleUsernameChange = (value) => {
this.setState({ usernameValue: value });
private handleUsernameChange = (usernameValue) => {
this.setState({ usernameValue });
};

private handlePasswordChange = (passwordValue) => {
Expand Down

0 comments on commit 92afdcc

Please sign in to comment.