diff --git a/CHANGES/2625.bug b/CHANGES/2625.bug new file mode 100644 index 0000000000..31ee1f94d3 --- /dev/null +++ b/CHANGES/2625.bug @@ -0,0 +1 @@ +Add autocomplete=off to login form fields diff --git a/src/components/index.ts b/src/components/index.ts index 90fc4ad018..09e42da5e4 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -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'; diff --git a/src/components/patternfly-wrappers/login-form.tsx b/src/components/patternfly-wrappers/login-form.tsx new file mode 100644 index 0000000000..6c93aad837 --- /dev/null +++ b/src/components/patternfly-wrappers/login-form.tsx @@ -0,0 +1,200 @@ +// this comes from @patternfly/react-core@4.276.11 +// 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, '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, + ) => 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, + ) => 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, + ) => 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, + ) => void; +} + +export const LoginForm: React.FunctionComponent = ({ + 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 = ( + + ); + + return ( +
+ + {helperText} + + + + + + {isShowPasswordEnabled && ( + + {passwordInput} + + + )} + {!isShowPasswordEnabled && passwordInput} + + {rememberMeLabel.length > 0 && ( + + + + )} + + + +
+ ); +}; +LoginForm.displayName = 'LoginForm'; diff --git a/src/components/patternfly-wrappers/write-only-field.tsx b/src/components/patternfly-wrappers/write-only-field.tsx index 06e000b642..a73c666eaf 100644 --- a/src/components/patternfly-wrappers/write-only-field.tsx +++ b/src/components/patternfly-wrappers/write-only-field.tsx @@ -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 */ @@ -13,28 +13,22 @@ interface IProps { children: React.ReactNode; } -export class WriteOnlyField extends React.Component { - render() { - const { onClear, isValueSet, children } = this.props; - - if (!isValueSet) { - return children; - } - - return ( - - - {isValueSet && ( - - )} - - ); - } -} +export const WriteOnlyField = ({ onClear, isValueSet, children }: IProps) => + !isValueSet ? ( + <>{children} + ) : ( + + + {isValueSet && ( + + )} + + ); diff --git a/src/components/rbac/user-form.tsx b/src/components/rbac/user-form.tsx index f97fe28c97..93f8432458 100644 --- a/src/components/rbac/user-form.tsx +++ b/src/components/rbac/user-form.tsx @@ -7,7 +7,6 @@ import { Label, Switch, TextInput, - TextInputTypes, Tooltip, } from '@patternfly/react-core'; import * as React from 'react'; @@ -90,7 +89,7 @@ export class UserForm extends React.Component { !isReadonly && { id: 'password', title: t`Password`, - type: TextInputTypes.password, + type: 'password', placeholder: isNewUser ? '' : '••••••••••••••••••••••', formGroupLabelIcon: ( { this.setState({ passwordConfirm: value }); }} type='password' + autoComplete='off' /> ); diff --git a/src/components/repositories/remote-form.tsx b/src/components/repositories/remote-form.tsx index 121f15ef07..e783ba8edd 100644 --- a/src/components/repositories/remote-form.tsx +++ b/src/components/repositories/remote-form.tsx @@ -260,6 +260,7 @@ export class RemoteForm extends React.Component { 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')} @@ -405,6 +406,7 @@ export class RemoteForm extends React.Component { isDisabled={disabledFields.includes('password')} id='password' type='password' + autoComplete='off' value={remote.password || ''} onChange={(value) => this.updateRemote(value, 'password')} /> @@ -483,6 +485,7 @@ export class RemoteForm extends React.Component { isDisabled={disabledFields.includes('proxy_password')} id='proxy_password' type='password' + autoComplete='off' value={remote.proxy_password || ''} onChange={(value) => this.updateRemote(value, 'proxy_password') diff --git a/src/components/shared/data-form.tsx b/src/components/shared/data-form.tsx index 38df25867f..dba13534a0 100644 --- a/src/components/shared/data-form.tsx +++ b/src/components/shared/data-form.tsx @@ -14,7 +14,7 @@ interface IProps { id: string; placeholder?: string; title: string; - type?: TextInputTypes; + type?: string; }[]; formPrefix?: React.ReactNode; formSuffix?: React.ReactNode; @@ -64,9 +64,10 @@ export class DataForm extends React.Component { 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' } : {})} /> )} diff --git a/src/containers/login/login.tsx b/src/containers/login/login.tsx index 0709fe1d1d..6d1bbcc7ac 100644 --- a/src/containers/login/login.tsx +++ b/src/containers/login/login.tsx @@ -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/'; @@ -70,8 +71,8 @@ class LoginPage extends React.Component { ); } - private handleUsernameChange = (value) => { - this.setState({ usernameValue: value }); + private handleUsernameChange = (usernameValue) => { + this.setState({ usernameValue }); }; private handlePasswordChange = (passwordValue) => {