Allows users to update their email address (#1232)

This commit is contained in:
Mike Stone 2017-02-24 10:08:59 -05:00 committed by Jason Meller
parent 1bb1c959ae
commit cc37cfa828
22 changed files with 677 additions and 24 deletions

View File

@ -0,0 +1,45 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import helpers from 'components/EmailTokenRedirect/helpers';
import userInterface from 'interfaces/user';
export class EmailTokenRedirect extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
token: PropTypes.string.isRequired,
user: userInterface,
};
componentWillMount () {
const { dispatch, token, user } = this.props;
return helpers.confirmEmailChange(dispatch, token, user);
}
componentWillReceiveProps (nextProps) {
const { dispatch, token: newToken, user: newUser } = nextProps;
const { token: oldToken, user: oldUser } = this.props;
const missingProps = !oldToken || !oldUser;
if (missingProps) {
return helpers.confirmEmailChange(dispatch, newToken, newUser);
}
return false;
}
render () {
return <div />;
}
}
const mapStateToProps = (state, { params }) => {
const { token } = params;
const { user } = state.auth;
return { token, user };
};
export default connect(mapStateToProps)(EmailTokenRedirect);

View File

@ -0,0 +1,67 @@
import React from 'react';
import expect, { spyOn, restoreSpies } from 'expect';
import { mount } from 'enzyme';
import { connectedComponent, reduxMockStore } from 'test/helpers';
import ConnectedEmailTokenRedirect, { EmailTokenRedirect } from 'components/EmailTokenRedirect/EmailTokenRedirect';
import Kolide from 'kolide';
import { userStub } from 'test/stubs';
describe('EmailTokenRedirect - component', () => {
afterEach(restoreSpies);
beforeEach(() => {
spyOn(Kolide.users, 'confirmEmailChange')
.andReturn(Promise.resolve({ ...userStub, email: 'new@email.com' }));
});
const authStore = {
auth: {
user: userStub,
},
};
const token = 'KFBR392';
const defaultProps = {
params: {
token,
},
};
describe('componentWillMount', () => {
it('calls the API when a token and user are present', () => {
const mockStore = reduxMockStore(authStore);
mount(connectedComponent(ConnectedEmailTokenRedirect, {
mockStore,
props: defaultProps,
}));
expect(Kolide.users.confirmEmailChange).toHaveBeenCalledWith(userStub, token);
});
it('does not call the API when only a token is present', () => {
const mockStore = reduxMockStore({ auth: {} });
mount(connectedComponent(ConnectedEmailTokenRedirect, {
mockStore,
props: defaultProps,
}));
expect(Kolide.users.confirmEmailChange).toNotHaveBeenCalled();
});
});
describe('componentWillReceiveProps', () => {
it('calls the API when a user is received', () => {
const mockStore = reduxMockStore();
const props = { dispatch: mockStore.dispatch, token };
const Component = mount(<EmailTokenRedirect {...props} />);
expect(Kolide.users.confirmEmailChange).toNotHaveBeenCalled();
Component.setProps({ user: userStub });
expect(Kolide.users.confirmEmailChange).toHaveBeenCalledWith(userStub, token);
});
});
});

View File

@ -0,0 +1,25 @@
import PATHS from 'router/paths';
import { push } from 'react-router-redux';
import { renderFlash } from 'redux/nodes/notifications/actions';
import userActions from 'redux/nodes/entities/users/actions';
const confirmEmailChange = (dispatch, token, user) => {
if (user && token) {
return dispatch(userActions.confirmEmailChange(user, token))
.then(() => {
dispatch(push(PATHS.USER_SETTINGS));
dispatch(renderFlash('success', 'Email updated successfully!'));
return false;
})
.catch(() => {
dispatch(push(PATHS.LOGIN));
return false;
});
}
return Promise.resolve();
};
export default { confirmEmailChange };

View File

@ -0,0 +1,122 @@
import expect, { spyOn, restoreSpies } from 'expect';
import { reduxMockStore } from 'test/helpers';
import helpers from 'components/EmailTokenRedirect/helpers';
import Kolide from 'kolide';
import { userStub } from 'test/stubs';
describe('EmailTokenRedirect - helpers', () => {
afterEach(restoreSpies);
describe('#confirmEmailChage', () => {
const { confirmEmailChange } = helpers;
const token = 'KFBR392';
const authStore = {
auth: {
user: userStub,
},
};
describe('successfully dispatching the confirmEmailChange action', () => {
beforeEach(() => {
spyOn(Kolide.users, 'confirmEmailChange')
.andReturn(Promise.resolve({ ...userStub, email: 'new@email.com' }));
});
it('pushes the user to the settings page', (done) => {
const mockStore = reduxMockStore(authStore);
const { dispatch } = mockStore;
confirmEmailChange(dispatch, userStub, token)
.then(() => {
const dispatchedActions = mockStore.getActions();
expect(dispatchedActions).toInclude({
type: '@@router/CALL_HISTORY_METHOD',
payload: {
method: 'push',
args: ['/settings'],
},
});
done();
})
.catch(done);
});
});
describe('unsuccessfully dispatching the confirmEmailChange action', () => {
beforeEach(() => {
const errors = [
{
name: 'base',
reason: 'Unable to confirm your email address',
},
];
const errorResponse = {
status: 422,
message: {
message: 'Unable to confirm email address',
errors,
},
};
spyOn(Kolide.users, 'confirmEmailChange')
.andReturn(Promise.reject(errorResponse));
});
it('pushes the user to the login page', (done) => {
const mockStore = reduxMockStore(authStore);
const { dispatch } = mockStore;
confirmEmailChange(dispatch, userStub, token)
.then(done)
.catch(() => {
const dispatchedActions = mockStore.getActions();
expect(dispatchedActions).toInclude({
type: '@@router/CALL_HISTORY_METHOD',
payload: {
method: 'push',
args: ['/login'],
},
});
done();
});
});
});
describe('when the user or token are not present', () => {
it('does not dispatch any actions when the user is not present', (done) => {
const mockStore = reduxMockStore(authStore);
const { dispatch } = mockStore;
confirmEmailChange(dispatch, undefined, token)
.then(() => {
const dispatchedActions = mockStore.getActions();
expect(dispatchedActions).toEqual([]);
done();
})
.catch(done);
});
it('does not dispatch any actions when the token is not present', (done) => {
const mockStore = reduxMockStore(authStore);
const { dispatch } = mockStore;
confirmEmailChange(dispatch, userStub, undefined)
.then(() => {
const dispatchedActions = mockStore.getActions();
expect(dispatchedActions).toEqual([]);
done();
})
.catch(done);
});
});
});
});

View File

@ -0,0 +1 @@
export default from './EmailTokenRedirect';

View File

@ -0,0 +1,53 @@
import React, { Component, PropTypes } from 'react';
import Button from 'components/buttons/Button';
import Form from 'components/forms/Form';
import formFieldInterface from 'interfaces/form_field';
import InputField from 'components/forms/fields/InputField';
const baseClass = 'change-email-form';
class ChangeEmailForm extends Component {
static propTypes = {
fields: PropTypes.shape({
password: formFieldInterface.isRequired,
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
};
render () {
const { fields, handleSubmit, onCancel } = this.props;
return (
<form onSubmit={handleSubmit}>
<InputField
{...fields.password}
autofocus
label="Password"
type="password"
/>
<div className={`${baseClass}__btn-wrap`}>
<Button className={`${baseClass}__btn`} type="submit" variant="brand">
Submit
</Button>
<Button onClick={onCancel} variant="inverse" className={`${baseClass}__btn`}>Cancel</Button>
</div>
</form>
);
}
}
export default Form(ChangeEmailForm, {
fields: ['password'],
validate: (formData) => {
if (!formData.password) {
return {
valid: false,
errors: { password: 'Password must be present' },
};
}
return { valid: true, errors: {} };
},
});

View File

@ -0,0 +1,15 @@
.change-email-form {
&__btn-wrap {
@include display(flex);
@include flex-direction(row-reverse);
}
&__btn {
font-size: $small;
height: 38px;
margin-bottom: 5px;
margin-left: 15px;
padding: 0;
width: 120px;
}
}

View File

@ -0,0 +1 @@
export default from './ChangeEmailForm';

View File

@ -18,11 +18,27 @@ class UserSettingsForm extends Component {
username: formFieldInterface.isRequired,
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
pendingEmail: PropTypes.string,
onCancel: PropTypes.func.isRequired,
};
renderEmailHint = () => {
const { pendingEmail } = this.props;
if (!pendingEmail) {
return undefined;
}
return (
<i className={`${baseClass}__email-hint`}>
Pending change to <b>{pendingEmail}</b>
</i>
);
}
render () {
const { fields, handleSubmit, onCancel } = this.props;
const { renderEmailHint } = this;
return (
<form onSubmit={handleSubmit} className={baseClass}>
@ -34,6 +50,7 @@ class UserSettingsForm extends Component {
<InputField
{...fields.email}
label="Email (required)"
hint={renderEmailHint()}
/>
<InputField
{...fields.name}

View File

@ -3,6 +3,10 @@
width: 75%;
}
&__email-hint {
display: block;
}
&__button-wrap {
width: 75%;
border-top: 1px solid $accent-medium;

View File

@ -6,6 +6,8 @@
}
&__content {
margin-top: 10px;
.input-field {
width: 100%;
}

View File

@ -2,6 +2,9 @@ export default {
CHANGE_PASSWORD: '/v1/kolide/change_password',
CONFIG: '/v1/kolide/config',
CONFIG_OPTIONS: '/v1/kolide/options',
CONFIRM_EMAIL_CHANGE: (token) => {
return `/v1/kolide/email/change/${token}`;
},
ENABLE_USER: (id) => {
return `/v1/kolide/users/${id}/enable`;
},

View File

@ -204,6 +204,15 @@ class Kolide extends Base {
return this.authenticatedPost(this.endpoint(CHANGE_PASSWORD), JSON.stringify(passwordParams));
},
confirmEmailChange: (user, token) => {
const { CONFIRM_EMAIL_CHANGE } = endpoints;
const endpoint = this.endpoint(CONFIRM_EMAIL_CHANGE(token));
return this.authenticatedGet(endpoint)
.then((response) => {
return { ...user, email: response.new_email };
});
},
enable: (user, { enabled }) => {
const { ENABLE_USER } = endpoints;

View File

@ -19,6 +19,7 @@ const {
invalidForgotPasswordRequest,
invalidResetPasswordRequest,
validChangePasswordRequest,
validConfirmEmailChangeRequest,
validCreateLabelRequest,
validCreateLicenseRequest,
validCreatePackRequest,
@ -544,6 +545,23 @@ describe('Kolide - API client', () => {
});
});
describe('#confirmEmailChange', () => {
it('calls the appropriate endpoint with the correct parameters', (done) => {
const token = 'KFBR392';
const request = validConfirmEmailChangeRequest(bearerToken, token);
Kolide.setBearerToken(bearerToken);
Kolide.users.confirmEmailChange(userStub, token)
.then(() => {
expect(request.isDone()).toEqual(true);
done();
})
.catch(() => {
throw new Error('Expected request to have been stubbed');
});
});
});
describe('#enable', () => {
it('calls the appropriate endpoint with the correct parameters', (done) => {
const enableParams = { enabled: true };

View File

@ -5,6 +5,7 @@ import moment from 'moment';
import Avatar from 'components/Avatar';
import Button from 'components/buttons/Button';
import ChangeEmailForm from 'components/forms/ChangeEmailForm';
import ChangePasswordForm from 'components/forms/ChangePasswordForm';
import deepDifference from 'utilities/deep_difference';
import Icon from 'components/icons/Icon';
@ -35,7 +36,12 @@ export class UserSettingsPage extends Component {
constructor (props) {
super(props);
this.state = { showModal: false };
this.state = {
pendingEmail: undefined,
showEmailModal: false,
showPasswordModal: false,
updatedUser: {},
};
}
onCancel = (evt) => {
@ -61,17 +67,28 @@ export class UserSettingsPage extends Component {
onShowModal = (evt) => {
evt.preventDefault();
this.setState({ showModal: true });
this.setState({ showPasswordModal: true });
return false;
}
onToggleModal = (evt) => {
onToggleEmailModal = (updatedUser = {}) => {
const { showEmailModal } = this.state;
this.setState({
showEmailModal: !showEmailModal,
updatedUser,
});
return false;
}
onTogglePasswordModal = (evt) => {
evt.preventDefault();
const { showModal } = this.state;
const { showPasswordModal } = this.state;
this.setState({ showModal: !showModal });
this.setState({ showPasswordModal: !showPasswordModal });
return false;
}
@ -80,9 +97,19 @@ export class UserSettingsPage extends Component {
const { dispatch, user } = this.props;
const updatedUser = deepDifference(formData, user);
if (updatedUser.email && !updatedUser.password) {
return this.onToggleEmailModal(updatedUser);
}
return dispatch(updateUser(user, updatedUser))
.then(() => {
return dispatch(renderFlash('success', 'Account updated!'));
if (updatedUser.email) {
this.setState({ pendingEmail: updatedUser.email });
}
dispatch(renderFlash('success', 'Account updated!'));
return true;
})
.catch(() => false);
}
@ -93,29 +120,60 @@ export class UserSettingsPage extends Component {
return dispatch(userActions.changePassword(user, formData))
.then(() => {
dispatch(renderFlash('success', 'Password changed successfully'));
this.setState({ showModal: false });
this.setState({ showPasswordModal: false });
return false;
});
}
renderModal = () => {
const { userErrors } = this.props;
const { showModal } = this.state;
const { handleSubmitPasswordForm, onToggleModal } = this;
renderEmailModal = () => {
const { errors } = this.props;
const { updatedUser, showEmailModal } = this.state;
const { handleSubmit, onToggleEmailModal } = this;
if (!showModal) {
const emailSubmit = (formData) => {
handleSubmit(formData)
.then((r) => {
return r ? onToggleEmailModal() : false;
});
};
if (!showEmailModal) {
return false;
}
return (
<Modal
title="To change your email you must supply your password"
onExit={onToggleEmailModal}
>
<ChangeEmailForm
formData={updatedUser}
handleSubmit={emailSubmit}
onCancel={onToggleEmailModal}
serverErrors={errors}
/>
</Modal>
);
}
renderPasswordModal = () => {
const { userErrors } = this.props;
const { showPasswordModal } = this.state;
const { handleSubmitPasswordForm, onTogglePasswordModal } = this;
if (!showPasswordModal) {
return false;
}
return (
<Modal
title="Change Password"
onExit={onToggleModal}
onExit={onTogglePasswordModal}
>
<ChangePasswordForm
handleSubmit={handleSubmitPasswordForm}
onCancel={onToggleModal}
onCancel={onTogglePasswordModal}
serverErrors={userErrors}
/>
</Modal>
@ -123,8 +181,16 @@ export class UserSettingsPage extends Component {
}
render () {
const { handleSubmit, onCancel, onLogout, onShowModal, renderModal } = this;
const {
handleSubmit,
onCancel,
onLogout,
onShowModal,
renderEmailModal,
renderPasswordModal,
} = this;
const { errors, user } = this.props;
const { pendingEmail } = this.state;
if (!user) {
return false;
@ -142,6 +208,7 @@ export class UserSettingsPage extends Component {
formData={user}
handleSubmit={handleSubmit}
onCancel={onCancel}
pendingEmail={pendingEmail}
serverErrors={errors}
/>
</div>
@ -169,7 +236,8 @@ export class UserSettingsPage extends Component {
LOGOUT
</Button>
</div>
{renderModal()}
{renderEmailModal()}
{renderPasswordModal()}
</div>
);
}

View File

@ -8,18 +8,22 @@ import testHelpers from 'test/helpers';
import { userStub } from 'test/stubs';
import * as authActions from 'redux/nodes/auth/actions';
const { connectedComponent, reduxMockStore } = testHelpers;
const {
connectedComponent,
fillInFormInput,
reduxMockStore,
} = testHelpers;
describe('UserSettingsPage - component', () => {
afterEach(restoreSpies);
it('renders a UserSettingsForm component', () => {
const store = { auth: { user: userStub }, entities: { users: {} } };
const mockStore = reduxMockStore(store);
const page = mount(connectedComponent(ConnectedPage, { mockStore }));
it('renders a UserSettingsForm component', () => {
const Page = mount(connectedComponent(ConnectedPage, { mockStore }));
expect(page.find('UserSettingsForm').length).toEqual(1);
expect(Page.find('UserSettingsForm').length).toEqual(1);
});
it('renders a UserSettingsForm component', () => {
@ -46,4 +50,45 @@ describe('UserSettingsPage - component', () => {
expect(authActions.updateUser).toHaveBeenCalledWith(userStub, updatedAttrs);
});
describe('changing email address', () => {
it('renders the ChangeEmailForm when the user changes their email', () => {
const Page = mount(connectedComponent(ConnectedPage, { mockStore }));
const UserSettingsForm = Page.find('UserSettingsForm');
const emailInput = UserSettingsForm.find({ name: 'email' });
expect(Page.find('ChangeEmailForm').length).toEqual(0, 'Expected the ChangeEmailForm to not render');
fillInFormInput(emailInput, 'new@email.org');
UserSettingsForm.simulate('submit');
expect(Page.find('ChangeEmailForm').length).toEqual(1, 'Expected the ChangeEmailForm to render');
});
it('does not render the ChangeEmailForm when the user does not change their email', () => {
const Page = mount(connectedComponent(ConnectedPage, { mockStore }));
const UserSettingsForm = Page.find('UserSettingsForm');
const emailInput = UserSettingsForm.find({ name: 'email' });
expect(Page.find('ChangeEmailForm').length).toEqual(0, 'Expected the ChangeEmailForm to not render');
fillInFormInput(emailInput, userStub.email);
UserSettingsForm.simulate('submit');
expect(Page.find('ChangeEmailForm').length).toEqual(0, 'Expected the ChangeEmailForm to not render');
});
it('displays pending email text when the user is pending an email change', () => {
const props = { dispatch: noop, user: userStub };
const Page = mount(<UserSettingsPage {...props} />);
const UserSettingsForm = () => Page.find('UserSettingsForm');
const emailHint = () => UserSettingsForm().find('.manage-user__email-hint');
expect(emailHint().length).toEqual(0, 'Expected the form to not render an email hint');
Page.setState({ pendingEmail: 'new@email.org' });
expect(emailHint().length).toEqual(1, 'Expected the form to render an email hint');
});
});
});

View File

@ -292,7 +292,7 @@ const reduxConfig = ({
dispatch(updateFailure(errorsObject));
throw errorsObject;
throw response;
});
};
};

View File

@ -2,6 +2,7 @@ import Kolide from 'kolide';
import config from 'redux/nodes/entities/users/config';
import { formatErrorResponse } from 'redux/nodes/entities/base/helpers';
import { logoutUser, updateUserSuccess } from 'redux/nodes/auth/actions';
const { extendedActions } = config;
@ -43,6 +44,29 @@ export const changePassword = (user, { new_password: newPassword, old_password:
};
};
export const confirmEmailChange = (user, token) => {
const { loadRequest, successAction, updateFailure, updateSuccess } = extendedActions;
return (dispatch) => {
dispatch(loadRequest);
return Kolide.users.confirmEmailChange(user, token)
.then((updatedUser) => {
dispatch(successAction(updatedUser, updateSuccess));
dispatch(updateUserSuccess(updatedUser));
return updatedUser;
})
.catch((response) => {
const errorsObject = formatErrorResponse(response);
dispatch(updateFailure(errorsObject));
return dispatch(logoutUser());
});
};
};
export const enableUser = (user, { enabled }) => {
const { successAction, updateFailure, updateSuccess } = extendedActions;
@ -96,4 +120,11 @@ export const updateAdmin = (user, { admin }) => {
};
};
export default { ...config.actions, changePassword, enableUser, requirePasswordReset, updateAdmin };
export default {
...config.actions,
changePassword,
confirmEmailChange,
enableUser,
requirePasswordReset,
updateAdmin,
};

View File

@ -3,9 +3,11 @@ import expect, { restoreSpies, spyOn } from 'expect';
import * as Kolide from 'kolide';
import { reduxMockStore } from 'test/helpers';
import { updateUserSuccess } from 'redux/nodes/auth/actions';
import {
changePassword,
confirmEmailChange,
enableUser,
requirePasswordReset,
REQUIRE_PASSWORD_RESET_FAILURE,
@ -211,6 +213,117 @@ describe('Users - actions', () => {
});
});
describe('confirmEmailChange', () => {
const token = 'KFBR392';
const updatedUser = { ...user, email: 'new@email.com' };
describe('successful request', () => {
beforeEach(() => {
spyOn(Kolide.default.users, 'confirmEmailChange').andCall(() => {
return Promise.resolve(updatedUser);
});
});
afterEach(restoreSpies);
it('calls the API', (done) => {
const mockStore = reduxMockStore(store);
mockStore.dispatch(confirmEmailChange(user, token))
.then(() => {
expect(Kolide.default.users.confirmEmailChange).toHaveBeenCalledWith(user, token);
done();
})
.catch(done);
});
it('dispatches the correct actions', (done) => {
const mockStore = reduxMockStore(store);
mockStore.dispatch(confirmEmailChange(user, token))
.then(() => {
const dispatchedActions = mockStore.getActions();
expect(dispatchedActions).toEqual([
config.extendedActions.loadRequest,
config.extendedActions.updateSuccess({
users: {
[user.id]: updatedUser,
},
}),
updateUserSuccess(updatedUser),
]);
done();
})
.catch(done);
});
});
describe('unsuccessful request', () => {
const errors = [
{
name: 'base',
reason: 'Unable to confirm your email address',
},
];
const errorResponse = {
status: 422,
message: {
message: 'Unable to confirm email address',
errors,
},
};
beforeEach(() => {
spyOn(Kolide.default.users, 'confirmEmailChange').andCall(() => {
return Promise.reject(errorResponse);
});
spyOn(Kolide.default, 'logout').andCall(() => {
return Promise.resolve({});
});
});
afterEach(restoreSpies);
it('calls the API', (done) => {
const mockStore = reduxMockStore(store);
mockStore.dispatch(confirmEmailChange(user, token))
.then(() => {
expect(Kolide.default.users.confirmEmailChange).toHaveBeenCalledWith(user, token);
done();
})
.catch(done);
});
it('dispatches the correct actions', (done) => {
const mockStore = reduxMockStore(store);
mockStore.dispatch(confirmEmailChange(user, token))
.then(() => {
const dispatchedActions = mockStore.getActions();
expect(dispatchedActions).toEqual([
config.extendedActions.loadRequest,
config.extendedActions.updateFailure({ base: 'Unable to confirm your email address', http_status: 422 }),
{ type: 'LOGOUT_REQUEST' },
{
type: '@@router/CALL_HISTORY_METHOD',
payload: {
method: 'push',
args: ['/login'],
},
},
{ type: 'LOGOUT_SUCCESS' },
]);
done();
})
.catch(done);
});
});
});
describe('updateAdmin', () => {
describe('successful request', () => {
beforeEach(() => {

View File

@ -13,6 +13,7 @@ import ConfigOptionsPage from 'pages/config/ConfigOptionsPage';
import ConfirmInvitePage from 'pages/ConfirmInvitePage';
import CoreLayout from 'layouts/CoreLayout';
import EditPackPage from 'pages/packs/EditPackPage';
import EmailTokenRedirect from 'components/EmailTokenRedirect';
import LicensePage from 'pages/LicensePage';
import LoginRoutes from 'components/LoginRoutes';
import LogoutPage from 'pages/LogoutPage';
@ -42,6 +43,7 @@ const routes = (
<Route path="reset" />
</Route>
<Route component={AuthenticatedRoutes}>
<Route path="email/change/:token" component={EmailTokenRedirect} />
<Route path="logout" component={LogoutPage} />
<Route component={CoreLayout}>
<IndexRedirect to="/hosts/manage" />

View File

@ -16,4 +16,5 @@ export default {
NEW_QUERY: '/queries/new',
RESET_PASSWORD: '/login/reset',
SETUP: '/setup',
USER_SETTINGS: '/settings',
};

View File

@ -24,6 +24,16 @@ export const validChangePasswordRequest = (bearerToken, params) => {
.reply(200, {});
};
export const validConfirmEmailChangeRequest = (bearerToken, token) => {
return nock('http://localhost:8080', {
reqHeaders: {
Authorization: `Bearer ${bearerToken}`,
},
})
.get(`/api/v1/kolide/email/change/${token}`)
.reply(200, { new_email: 'new@email.com' });
};
export const validCreateLabelRequest = (bearerToken, labelParams) => {
return nock('http://localhost:8080', {
reqHeaders: {
@ -518,6 +528,7 @@ export default {
invalidGetQueryRequest,
invalidResetPasswordRequest,
validChangePasswordRequest,
validConfirmEmailChangeRequest,
validCreateLabelRequest,
validCreateLicenseRequest,
validCreatePackRequest,