mirror of
https://github.com/zoriya/flood.git
synced 2026-06-02 11:06:35 +00:00
client: AuthForm: properly handle and display errors
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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', '');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user