Login: Show oauth error messages inline (#72255)

Squashed commit of the following:

commit b82ed875b88d12348cd4043e2add493e8fb6edc6
Author: Roxana Turc <anamaria-roxana.turc@grafana.com>
Date:   Tue Jul 25 10:42:31 2023 +0100

    user essentials mob! 🔱

    lastFile:public/app/core/components/Login/LoginPage.test.tsx

commit 923cbcf190d2def57b59f8b231f14322efdf9718
Author: joshhunt <josh@trtr.co>
Date:   Tue Jul 25 10:17:51 2023 +0100

    user essentials mob! 🔱

Co-authored-by: joshhunt <josh@trtr.co>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: L-M-K-B <48948963+L-M-K-B@users.noreply.github.com>
This commit is contained in:
RoxanaAnamariaTurc 2023-07-25 10:57:45 +01:00 committed by GitHub
parent e9ba6922c0
commit edb7d0e0d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 32 additions and 14 deletions

View File

@ -79,7 +79,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
disableUserSignUp = false; disableUserSignUp = false;
loginHint = ''; loginHint = '';
passwordHint = ''; passwordHint = '';
loginError = undefined; loginError: string | undefined = undefined;
viewersCanEdit = false; viewersCanEdit = false;
editorsCanAdmin = false; editorsCanAdmin = false;
disableSanitizeHtml = false; disableSanitizeHtml = false;

View File

@ -1,8 +1,6 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { AppEvents } from '@grafana/data';
import { FetchError, getBackendSrv, isFetchError } from '@grafana/runtime'; import { FetchError, getBackendSrv, isFetchError } from '@grafana/runtime';
import appEvents from 'app/core/app_events';
import config from 'app/core/config'; import config from 'app/core/config';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
@ -53,11 +51,8 @@ export class LoginCtrl extends PureComponent<Props, State> {
isLoggingIn: false, isLoggingIn: false,
isChangingPassword: false, isChangingPassword: false,
showDefaultPasswordWarning: false, showDefaultPasswordWarning: false,
loginErrorMessage: config.loginError,
}; };
if (config.loginError) {
appEvents.emit(AppEvents.alertWarning, ['Login Failed', config.loginError]);
}
} }
changePassword = (password: string) => { changePassword = (password: string) => {

View File

@ -105,6 +105,16 @@ describe('Login Page', () => {
expect(screen.getByRole('link', { name: 'Sign in with Okta Test' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'Sign in with Okta Test' })).toBeInTheDocument();
}); });
it('shows oauth errors', async () => {
runtimeMock.config.loginError = 'Oh no there was an error :(';
render(<LoginPage />);
const alert = await screen.findByRole('alert', { name: 'Login failed' });
expect(alert).toBeInTheDocument();
expect(alert).toHaveTextContent('Oh no there was an error :(');
});
it('shows an error with incorrect password', async () => { it('shows an error with incorrect password', async () => {
postMock.mockRejectedValueOnce({ postMock.mockRejectedValueOnce({
data: { data: {
@ -122,7 +132,9 @@ describe('Login Page', () => {
await userEvent.type(screen.getByLabelText('Password input field'), 'test'); await userEvent.type(screen.getByLabelText('Password input field'), 'test');
await userEvent.click(screen.getByRole('button', { name: 'Login button' })); await userEvent.click(screen.getByRole('button', { name: 'Login button' }));
expect(await screen.findByRole('alert', { name: 'Invalid username or password' })).toBeInTheDocument(); const alert = await screen.findByRole('alert', { name: 'Login failed' });
expect(alert).toBeInTheDocument();
expect(alert).toHaveTextContent('Invalid username or password');
}); });
it('shows a different error with failed login attempts', async () => { it('shows a different error with failed login attempts', async () => {
@ -142,10 +154,10 @@ describe('Login Page', () => {
await userEvent.type(screen.getByLabelText('Password input field'), 'test'); await userEvent.type(screen.getByLabelText('Password input field'), 'test');
await userEvent.click(screen.getByRole('button', { name: 'Login button' })); await userEvent.click(screen.getByRole('button', { name: 'Login button' }));
expect( const alert = await screen.findByRole('alert', { name: 'Login failed' });
await screen.findByRole('alert', { expect(alert).toBeInTheDocument();
name: 'You have exceeded the number of login attempts for this user. Please try again later.', expect(alert).toHaveTextContent(
}) 'You have exceeded the number of login attempts for this user. Please try again later.'
).toBeInTheDocument(); );
}); });
}); });

View File

@ -6,6 +6,7 @@ import React from 'react';
import { Alert, HorizontalGroup, LinkButton } from '@grafana/ui'; import { Alert, HorizontalGroup, LinkButton } from '@grafana/ui';
import { Branding } from 'app/core/components/Branding/Branding'; import { Branding } from 'app/core/components/Branding/Branding';
import config from 'app/core/config'; import config from 'app/core/config';
import { t } from 'app/core/internationalization';
import { ChangePassword } from '../ForgottenPassword/ChangePassword'; import { ChangePassword } from '../ForgottenPassword/ChangePassword';
@ -44,7 +45,11 @@ export const LoginPage = () => {
<LoginLayout isChangingPassword={isChangingPassword}> <LoginLayout isChangingPassword={isChangingPassword}>
{!isChangingPassword && ( {!isChangingPassword && (
<InnerBox> <InnerBox>
{loginErrorMessage && <Alert className={alertStyles} severity="error" title={loginErrorMessage} />} {loginErrorMessage && (
<Alert className={alertStyles} severity="error" title={t('login.error.title', 'Login failed')}>
{loginErrorMessage}
</Alert>
)}
{!disableLoginForm && ( {!disableLoginForm && (
<LoginForm onSubmit={login} loginHint={loginHint} passwordHint={passwordHint} isLoggingIn={isLoggingIn}> <LoginForm onSubmit={login} loginHint={loginHint} passwordHint={passwordHint} isLoggingIn={isLoggingIn}>

View File

@ -254,6 +254,7 @@
"error": { "error": {
"blocked": "", "blocked": "",
"invalid-user-or-password": "", "invalid-user-or-password": "",
"title": "",
"unknown": "" "unknown": ""
} }
}, },

View File

@ -254,6 +254,7 @@
"error": { "error": {
"blocked": "You have exceeded the number of login attempts for this user. Please try again later.", "blocked": "You have exceeded the number of login attempts for this user. Please try again later.",
"invalid-user-or-password": "Invalid username or password", "invalid-user-or-password": "Invalid username or password",
"title": "Login failed",
"unknown": "Unknown error occurred" "unknown": "Unknown error occurred"
} }
}, },

View File

@ -259,6 +259,7 @@
"error": { "error": {
"blocked": "", "blocked": "",
"invalid-user-or-password": "", "invalid-user-or-password": "",
"title": "",
"unknown": "" "unknown": ""
} }
}, },

View File

@ -259,6 +259,7 @@
"error": { "error": {
"blocked": "", "blocked": "",
"invalid-user-or-password": "", "invalid-user-or-password": "",
"title": "",
"unknown": "" "unknown": ""
} }
}, },

View File

@ -254,6 +254,7 @@
"error": { "error": {
"blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.", "blocked": "Ÿőū ĥävę ęχčęęđęđ ŧĥę ʼnūmþęř őƒ ľőģįʼn äŧŧęmpŧş ƒőř ŧĥįş ūşęř. Pľęäşę ŧřy äģäįʼn ľäŧęř.",
"invalid-user-or-password": "Ĩʼnväľįđ ūşęřʼnämę őř päşşŵőřđ", "invalid-user-or-password": "Ĩʼnväľįđ ūşęřʼnämę őř päşşŵőřđ",
"title": "Ŀőģįʼn ƒäįľęđ",
"unknown": "Ůʼnĸʼnőŵʼn ęřřőř őččūřřęđ" "unknown": "Ůʼnĸʼnőŵʼn ęřřőř őččūřřęđ"
} }
}, },

View File

@ -249,6 +249,7 @@
"error": { "error": {
"blocked": "", "blocked": "",
"invalid-user-or-password": "", "invalid-user-or-password": "",
"title": "",
"unknown": "" "unknown": ""
} }
}, },