client: AuthForm: properly handle and display errors

This commit is contained in:
Jesse Chan
2020-10-20 00:59:31 +08:00
parent a7d3638bc4
commit 2fdcfc899a
7 changed files with 111 additions and 71 deletions
+4 -7
View File
@@ -1,4 +1,4 @@
import axios from 'axios';
import axios, {AxiosError} from 'axios';
import type {
AuthAuthenticationOptions,
@@ -33,7 +33,7 @@ const AuthActions = {
// server's response.
let errorMessage;
if (error.response) {
if (error.response && error.response.data.message != null) {
errorMessage = error.response.data.message;
} else if (error.message) {
errorMessage = error.message;
@@ -133,11 +133,8 @@ const AuthActions = {
data,
});
},
(error) => {
AppDispatcher.dispatchServerAction({
type: 'AUTH_REGISTER_ERROR',
error: error.response.data.message,
});
(error: AxiosError) => {
throw error;
},
),
@@ -7,9 +7,7 @@ import type {Credentials} from '@shared/schema/Auth';
import {Button, Form, FormError, FormRow, Panel, PanelContent, PanelHeader, PanelFooter, Textbox} from '../../ui';
import AuthActions from '../../actions/AuthActions';
import AuthStore from '../../stores/AuthStore';
import ClientConnectionSettingsForm from '../general/connection-settings/ClientConnectionSettingsForm';
import connectStores from '../../util/connectStores';
import history from '../../util/history';
import type {ClientConnectionSettingsFormType} from '../general/connection-settings/ClientConnectionSettingsForm';
@@ -19,11 +17,11 @@ type RegisterFormData = Pick<Credentials, 'username' | 'password'>;
interface AuthFormProps extends WrappedComponentProps {
mode: 'login' | 'register';
error?: Error;
}
interface AuthFormStates extends Record<string, unknown> {
interface AuthFormStates {
isSubmitting: boolean;
errorMessage?: string;
}
class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
@@ -73,20 +71,22 @@ class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
this.setState({isSubmitting: true});
if (this.props.mode === 'login') {
const credentials = submission.formData as Partial<LoginFormData>;
const {intl, mode} = this.props;
if (
credentials.username == null ||
credentials.username === '' ||
credentials.password == null ||
credentials.password === ''
) {
this.setState({isSubmitting: false}, () => {
// do nothing.
});
return;
}
const formData = submission.formData as Partial<LoginFormData> | Partial<RegisterFormData>;
if (formData.username == null || formData.username === '') {
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'auth.error.username.empty'})});
return;
}
if (formData.password == null || formData.password === '') {
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'auth.error.password.empty'})});
return;
}
if (mode === 'login') {
const credentials = formData as LoginFormData;
AuthActions.authenticate({
username: credentials.username,
@@ -95,26 +95,20 @@ class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
.then(() => {
this.setState({isSubmitting: false}, () => history.replace('overview'));
})
.catch(() => {
this.setState({isSubmitting: false}, () => history.replace('login'));
.catch((error: Error) => {
this.setState({isSubmitting: false, errorMessage: error.message}, () => history.replace('login'));
});
} else {
const config = submission.formData as Partial<RegisterFormData>;
const config = formData as RegisterFormData;
if (
config.username == null ||
config.username === '' ||
config.password == null ||
config.password === '' ||
this.settingsFormRef.current == null
) {
this.setState({isSubmitting: false});
if (this.settingsFormRef.current == null) {
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'connection.settings.error.empty'})});
return;
}
const connectionSettings = this.settingsFormRef.current.getConnectionSettings();
if (connectionSettings == null) {
this.setState({isSubmitting: false});
this.setState({isSubmitting: false, errorMessage: intl.formatMessage({id: 'connection.settings.error.empty'})});
return;
}
@@ -123,15 +117,20 @@ class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
password: config.password,
client: connectionSettings,
level: AccessLevel.ADMINISTRATOR,
}).then(() => {
this.setState({isSubmitting: false}, () => history.replace('overview'));
});
}).then(
() => {
this.setState({isSubmitting: false}, () => history.replace('overview'));
},
(error: Error) => {
this.setState({isSubmitting: false, errorMessage: error.message});
},
);
}
};
render() {
const {isSubmitting} = this.state;
const {error, intl, mode} = this.props;
const {errorMessage, isSubmitting} = this.state;
const {intl, mode} = this.props;
const isLoginMode = mode === 'login';
return (
@@ -147,9 +146,9 @@ class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
</PanelHeader>
<PanelContent>
<p className="copy--lead">{this.getIntroText()}</p>
{error != null ? (
{errorMessage != null ? (
<FormRow>
<FormError isLoading={isSubmitting}>{error}</FormError>
<FormError isLoading={isSubmitting}>{errorMessage}</FormError>
</FormRow>
) : null}
<FormRow>
@@ -198,18 +197,4 @@ class AuthForm extends React.Component<AuthFormProps, AuthFormStates> {
}
}
const ConnectedAuthForm = connectStores<Omit<AuthFormProps, 'intl'>, AuthFormStates>(injectIntl(AuthForm), () => {
return [
{
store: AuthStore,
event: ['AUTH_LOGIN_ERROR', 'AUTH_REGISTER_ERROR'],
getValue: ({payload}) => {
return {
error: payload as AuthFormProps['error'],
};
},
},
];
});
export default ConnectedAuthForm;
export default injectIntl(AuthForm);
@@ -6,7 +6,6 @@ export type EventType =
| 'AUTH_LIST_USERS_SUCCESS'
| 'AUTH_LOGIN_ERROR'
| 'AUTH_LOGIN_SUCCESS'
| 'AUTH_REGISTER_ERROR'
| 'AUTH_REGISTER_SUCCESS'
| 'AUTH_VERIFY_ERROR'
| 'AUTH_VERIFY_SUCCESS'
@@ -12,7 +12,6 @@ import type {Feeds, Items, Rules} from '../stores/FeedsStore';
type ErrorType =
| 'AUTH_LOGIN_ERROR'
| 'AUTH_LOGOUT_ERROR'
| 'AUTH_REGISTER_ERROR'
| 'AUTH_VERIFY_ERROR'
| 'CLIENT_ADD_TORRENT_ERROR'
| 'FLOOD_CLEAR_NOTIFICATIONS_ERROR'
@@ -100,10 +100,6 @@ class AuthStoreClass extends BaseStore {
FloodActions.restartActivityStream();
}
handleRegisterError(error?: Error): void {
this.emit('AUTH_REGISTER_ERROR', error);
}
handleAuthVerificationSuccess(response: AuthVerificationResponse): void {
if (response.initialUser === true) {
this.currentUser.isInitialUser = response.initialUser;
@@ -160,9 +156,6 @@ AuthStore.dispatcherID = AppDispatcher.register((payload) => {
case 'AUTH_REGISTER_SUCCESS':
AuthStore.handleRegisterSuccess(action.data);
break;
case 'AUTH_REGISTER_ERROR':
AuthStore.handleRegisterError(action.error);
break;
case 'AUTH_VERIFY_SUCCESS':
AuthStore.handleAuthVerificationSuccess(action.data);
break;
+54
View File
@@ -0,0 +1,54 @@
context('Login', () => {
beforeEach(() => {
cy.server();
cy.route({method: 'GET', url: 'http://127.0.0.1:4200/api/auth/verify?*', response: {}, status: 401}).as(
'verify-request',
);
cy.visit('http://127.0.0.1:4200/login');
cy.url().should('include', 'login');
});
it('Login without username', () => {
cy.get('.input[name="password"]').type('test');
cy.get('.button[type="submit"]').click();
cy.get('.application__view--auth-form').should('be.visible');
cy.get('.application__content').should('not.be.visible');
cy.get('.application__loading-overlay').should('not.be.visible');
cy.get('.error').should('be.visible');
});
it('Login without password', () => {
cy.get('.input[name="username"]').type('test');
cy.get('.button[type="submit"]').click();
cy.get('.application__view--auth-form').should('be.visible');
cy.get('.application__content').should('not.be.visible');
cy.get('.application__loading-overlay').should('not.be.visible');
cy.get('.error').should('be.visible');
});
it('Login, server error occurred', () => {
cy.get('.input[name="username"]').type('test');
cy.get('.input[name="password"]').type('test');
cy.server();
cy.route({
method: 'POST',
url: 'http://127.0.0.1:4200/api/auth/authenticate',
status: 500,
}).as('verify-request');
cy.get('.button[type="submit"]').click();
cy.get('.application__view--auth-form').should('be.visible');
cy.get('.application__content').should('not.be.visible');
cy.get('.application__loading-overlay').should('not.be.visible');
cy.get('.error').should('be.visible');
});
it('Clear', () => {
cy.get('.input[name="username"]').type('test').as('password');
cy.get('.input[name="password"]').type('test').as('username');
cy.get('.button__content').contains('Clear').parent().click();
cy.get('@username').should('have.value', '');
cy.get('@password').should('have.value', '');
});
});
+17 -4
View File
@@ -1,5 +1,12 @@
context('Register', () => {
beforeEach(() => {
cy.server();
cy.route({
method: 'GET',
url: 'http://127.0.0.1:4200/api/auth/verify?*',
response: {initialUser: true},
status: 200,
}).as('verify-request');
cy.visit('http://127.0.0.1:4200/register');
cy.url().should('include', 'register');
});
@@ -38,6 +45,7 @@ context('Register', () => {
cy.get('.application__view--auth-form').should('be.visible');
cy.get('.application__content').should('not.be.visible');
cy.get('.application__loading-overlay').should('not.be.visible');
cy.get('.error').should('be.visible');
});
it('Register without password', () => {
@@ -51,6 +59,7 @@ context('Register', () => {
cy.get('.application__view--auth-form').should('be.visible');
cy.get('.application__content').should('not.be.visible');
cy.get('.application__loading-overlay').should('not.be.visible');
cy.get('.error').should('be.visible');
});
it('Register without connection settings', () => {
@@ -60,9 +69,10 @@ context('Register', () => {
cy.get('.application__view--auth-form').should('be.visible');
cy.get('.application__content').should('not.be.visible');
cy.get('.application__loading-overlay').should('not.be.visible');
cy.get('.error').should('be.visible');
});
it('Register with socket connection settings', () => {
it('Register with socket connection settings, server error occurred', () => {
cy.get('.input[name="username"]').type('test');
cy.get('.input[name="password"]').type('test');
cy.get('.select').click();
@@ -71,13 +81,16 @@ context('Register', () => {
cy.get('.input--text[name="socket"]').type('/data/rtorrent.sock');
cy.server();
cy.route({method: 'POST', url: 'http://127.0.0.1:4200/api/auth/register', response: {}, status: 403}).as(
cy.route({method: 'POST', url: 'http://127.0.0.1:4200/api/auth/register', response: {}, status: 500}).as(
'register-request',
);
cy.get('.button[type="submit"]').click();
cy.get('.application__view--auth-form').should('not.be.visible');
cy.get('.application__content').should('be.visible');
cy.get('.application__view--auth-form').should('be.visible');
cy.get('.application__content').should('not.be.visible');
cy.get('.application__loading-overlay').should('not.be.visible');
cy.get('.error').should('be.visible');
});
});