Login: Show additional information when prompting to change password (#69537)

* add some information to the password change screen

* update threshold cause pa11y is stupid

* override the loginlayout title if changing password

* remove top padding from inner box
This commit is contained in:
Ashley Harrison 2023-06-06 12:24:46 +02:00 committed by GitHub
parent c1d3a2d81e
commit 48e328c1dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 58 deletions

View File

@ -74,7 +74,7 @@ var config = {
"click element button[aria-label='Login button']", "click element button[aria-label='Login button']",
"wait for element [aria-label='Skip change password button'] to be visible", "wait for element [aria-label='Skip change password button'] to be visible",
], ],
threshold: 14, threshold: 15,
rootElement: '.main-view', rootElement: '.main-view',
}, },
{ {

View File

@ -1,13 +1,14 @@
import React, { SyntheticEvent } from 'react'; import React, { SyntheticEvent } from 'react';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { Tooltip, Form, Field, VerticalGroup, Button } from '@grafana/ui'; import { Tooltip, Form, Field, VerticalGroup, Button, Alert } from '@grafana/ui';
import { submitButton } from '../Login/LoginForm'; import { submitButton } from '../Login/LoginForm';
import { PasswordField } from '../PasswordField/PasswordField'; import { PasswordField } from '../PasswordField/PasswordField';
interface Props { interface Props {
onSubmit: (pw: string) => void; onSubmit: (pw: string) => void;
onSkip?: (event?: SyntheticEvent) => void; onSkip?: (event?: SyntheticEvent) => void;
showDefaultPasswordWarning?: boolean;
} }
interface PasswordDTO { interface PasswordDTO {
@ -15,7 +16,7 @@ interface PasswordDTO {
confirmNew: string; confirmNew: string;
} }
export const ChangePassword = ({ onSubmit, onSkip }: Props) => { export const ChangePassword = ({ onSubmit, onSkip, showDefaultPasswordWarning }: Props) => {
const submit = (passwords: PasswordDTO) => { const submit = (passwords: PasswordDTO) => {
onSubmit(passwords.newPassword); onSubmit(passwords.newPassword);
}; };
@ -23,6 +24,9 @@ export const ChangePassword = ({ onSubmit, onSkip }: Props) => {
<Form onSubmit={submit}> <Form onSubmit={submit}>
{({ errors, register, getValues }) => ( {({ errors, register, getValues }) => (
<> <>
{showDefaultPasswordWarning && (
<Alert severity="info" title="Continuing to use the default password exposes you to security risks." />
)}
<Field label="New password" invalid={!!errors.newPassword} error={errors?.newPassword?.message}> <Field label="New password" invalid={!!errors.newPassword} error={errors?.newPassword?.message}>
<PasswordField <PasswordField
id="new-password" id="new-password"

View File

@ -11,7 +11,7 @@ export interface Props extends GrafanaRouteComponentProps<{}, { code: string }>
export const ChangePasswordPage = (props: Props) => { export const ChangePasswordPage = (props: Props) => {
return ( return (
<LoginLayout> <LoginLayout isChangingPassword>
<InnerBox> <InnerBox>
<LoginCtrl resetCode={props.queryParams.code}> <LoginCtrl resetCode={props.queryParams.code}>
{({ changePassword }) => <ChangePassword onSubmit={changePassword} />} {({ changePassword }) => <ChangePassword onSubmit={changePassword} />}

View File

@ -31,12 +31,14 @@ interface Props {
isOauthEnabled: boolean; isOauthEnabled: boolean;
loginHint: string; loginHint: string;
passwordHint: string; passwordHint: string;
showDefaultPasswordWarning: boolean;
}) => JSX.Element; }) => JSX.Element;
} }
interface State { interface State {
isLoggingIn: boolean; isLoggingIn: boolean;
isChangingPassword: boolean; isChangingPassword: boolean;
showDefaultPasswordWarning: boolean;
} }
export class LoginCtrl extends PureComponent<Props, State> { export class LoginCtrl extends PureComponent<Props, State> {
@ -47,6 +49,7 @@ export class LoginCtrl extends PureComponent<Props, State> {
this.state = { this.state = {
isLoggingIn: false, isLoggingIn: false,
isChangingPassword: false, isChangingPassword: false,
showDefaultPasswordWarning: false,
}; };
if (config.loginError) { if (config.loginError) {
@ -96,7 +99,7 @@ export class LoginCtrl extends PureComponent<Props, State> {
this.toGrafana(); this.toGrafana();
return; return;
} else { } else {
this.changeView(); this.changeView(formModel.password === 'admin');
} }
}) })
.catch(() => { .catch(() => {
@ -106,9 +109,10 @@ export class LoginCtrl extends PureComponent<Props, State> {
}); });
}; };
changeView = () => { changeView = (showDefaultPasswordWarning: boolean) => {
this.setState({ this.setState({
isChangingPassword: true, isChangingPassword: true,
showDefaultPasswordWarning,
}); });
}; };
@ -127,7 +131,7 @@ export class LoginCtrl extends PureComponent<Props, State> {
render() { render() {
const { children } = this.props; const { children } = this.props;
const { isLoggingIn, isChangingPassword } = this.state; const { isLoggingIn, isChangingPassword, showDefaultPasswordWarning } = this.state;
const { login, toGrafana, changePassword } = this; const { login, toGrafana, changePassword } = this;
const { loginHint, passwordHint, disableLoginForm, disableUserSignUp } = config; const { loginHint, passwordHint, disableLoginForm, disableUserSignUp } = config;
@ -144,6 +148,7 @@ export class LoginCtrl extends PureComponent<Props, State> {
changePassword, changePassword,
skipPasswordChange: toGrafana, skipPasswordChange: toGrafana,
isChangingPassword, isChangingPassword,
showDefaultPasswordWarning,
})} })}
</> </>
); );

View File

@ -19,9 +19,10 @@ export const InnerBox = ({ children, enterAnimation = true }: React.PropsWithChi
export interface LoginLayoutProps { export interface LoginLayoutProps {
/** Custom branding settings that can be used e.g. for previewing the Login page changes */ /** Custom branding settings that can be used e.g. for previewing the Login page changes */
branding?: BrandingSettings; branding?: BrandingSettings;
isChangingPassword?: boolean;
} }
export const LoginLayout = ({ children, branding }: React.PropsWithChildren<LoginLayoutProps>) => { export const LoginLayout = ({ children, branding, isChangingPassword }: React.PropsWithChildren<LoginLayoutProps>) => {
const loginStyles = useStyles2(getLoginStyles); const loginStyles = useStyles2(getLoginStyles);
const [startAnim, setStartAnim] = useState(false); const [startAnim, setStartAnim] = useState(false);
const subTitle = branding?.loginSubtitle ?? Branding.GetLoginSubTitle(); const subTitle = branding?.loginSubtitle ?? Branding.GetLoginSubTitle();
@ -39,8 +40,14 @@ export const LoginLayout = ({ children, branding }: React.PropsWithChildren<Logi
<div className={loginStyles.loginLogoWrapper}> <div className={loginStyles.loginLogoWrapper}>
<Branding.LoginLogo className={loginStyles.loginLogo} logo={loginLogo} /> <Branding.LoginLogo className={loginStyles.loginLogo} logo={loginLogo} />
<div className={loginStyles.titleWrapper}> <div className={loginStyles.titleWrapper}>
<h1 className={loginStyles.mainTitle}>{loginTitle}</h1> {isChangingPassword ? (
{subTitle && <h3 className={loginStyles.subTitle}>{subTitle}</h3>} <h1 className={loginStyles.mainTitle}>Update your password</h1>
) : (
<>
<h1 className={loginStyles.mainTitle}>{loginTitle}</h1>
{subTitle && <h3 className={loginStyles.subTitle}>{subTitle}</h3>}
</>
)}
</div> </div>
</div> </div>
<div className={loginStyles.loginOuterBox}>{children}</div> <div className={loginStyles.loginOuterBox}>{children}</div>
@ -144,7 +151,7 @@ export const getLoginStyles = (theme: GrafanaTheme2) => {
justify-content: center; justify-content: center;
`, `,
loginInnerBox: css` loginInnerBox: css`
padding: ${theme.spacing(2)}; padding: ${theme.spacing(0, 2, 2, 2)};
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -23,52 +23,50 @@ const forgottenPasswordStyles = css`
export const LoginPage = () => { export const LoginPage = () => {
document.title = Branding.AppTitle; document.title = Branding.AppTitle;
return ( return (
<LoginLayout> <LoginCtrl>
<LoginCtrl> {({
{({ loginHint,
loginHint, passwordHint,
passwordHint, disableLoginForm,
disableLoginForm, disableUserSignUp,
disableUserSignUp, login,
login, isLoggingIn,
isLoggingIn, changePassword,
changePassword, skipPasswordChange,
skipPasswordChange, isChangingPassword,
isChangingPassword, showDefaultPasswordWarning,
}) => ( }) => (
<> <LoginLayout isChangingPassword={isChangingPassword}>
{!isChangingPassword && ( {!isChangingPassword && (
<InnerBox> <InnerBox>
{!disableLoginForm && ( {!disableLoginForm && (
<LoginForm <LoginForm onSubmit={login} loginHint={loginHint} passwordHint={passwordHint} isLoggingIn={isLoggingIn}>
onSubmit={login} <HorizontalGroup justify="flex-end">
loginHint={loginHint} <LinkButton
passwordHint={passwordHint} className={forgottenPasswordStyles}
isLoggingIn={isLoggingIn} fill="text"
> href={`${config.appSubUrl}/user/password/send-reset-email`}
<HorizontalGroup justify="flex-end"> >
<LinkButton Forgot your password?
className={forgottenPasswordStyles} </LinkButton>
fill="text" </HorizontalGroup>
href={`${config.appSubUrl}/user/password/send-reset-email`} </LoginForm>
> )}
Forgot your password? <LoginServiceButtons />
</LinkButton> {!disableUserSignUp && <UserSignup />}
</HorizontalGroup> </InnerBox>
</LoginForm> )}
)} {isChangingPassword && (
<LoginServiceButtons /> <InnerBox>
{!disableUserSignUp && <UserSignup />} <ChangePassword
</InnerBox> showDefaultPasswordWarning={showDefaultPasswordWarning}
)} onSubmit={changePassword}
{isChangingPassword && ( onSkip={() => skipPasswordChange()}
<InnerBox> />
<ChangePassword onSubmit={changePassword} onSkip={() => skipPasswordChange()} /> </InnerBox>
</InnerBox> )}
)} </LoginLayout>
</> )}
)} </LoginCtrl>
</LoginCtrl>
</LoginLayout>
); );
}; };