From 2bb2bf2d5d7cc4ecbaae2fcf30f0a1a890fcc455 Mon Sep 17 00:00:00 2001
From: gillespi314 <73313222+gillespi314@users.noreply.github.com>
Date: Mon, 12 Jul 2021 10:26:11 -0500
Subject: [PATCH] Add ability in Fleet UI for admin to create new users without
email invitations (#1261)
---
changes/1261-admin-create-user-no-invite | 8 +
.../integration/all/app/activateuser.spec.ts | 4 +
.../DataTable/DropdownCell/_styles.scss | 2 +-
.../components/forms/fields/Radio/Radio.tsx | 6 +-
frontend/fleet/endpoints.ts | 1 +
frontend/fleet/entities/users.js | 10 +
.../UserManagementPage/UserManagementPage.jsx | 118 ++++++------
.../UserManagementPage.tests.jsx | 139 --------------
.../UserManagementPage/UsersTableConfig.tsx | 2 +-
.../components/UserForm/UserForm.tsx | 171 +++++++++++++++++-
.../components/UserForm/_styles.scss | 43 +++++
.../helpers/userManagementHelpers.tests.ts | 3 +-
.../redux/nodes/entities/users/actions.js | 38 ++++
.../redux/nodes/entities/users/reducer.js | 18 ++
14 files changed, 347 insertions(+), 216 deletions(-)
create mode 100644 changes/1261-admin-create-user-no-invite
delete mode 100644 frontend/pages/admin/UserManagementPage/UserManagementPage.tests.jsx
diff --git a/changes/1261-admin-create-user-no-invite b/changes/1261-admin-create-user-no-invite
new file mode 100644
index 0000000000..ebee18ee0e
--- /dev/null
+++ b/changes/1261-admin-create-user-no-invite
@@ -0,0 +1,8 @@
+- Add `USERS_ADMIN` endpoint to `fleet/endpoints`
+- Add `createUserWithoutInvitation` action to `redux/nodes/entities/users`
+- Revise `UserManagementPage`
+- Revise `UserForm`
+- Update existing Cypress test
+- Remove `renderSmtpWarning` from `UserManagementPage`
+
+Implements #369
\ No newline at end of file
diff --git a/cypress/integration/all/app/activateuser.spec.ts b/cypress/integration/all/app/activateuser.spec.ts
index 0a2ca9aab8..d3f7e811e8 100644
--- a/cypress/integration/all/app/activateuser.spec.ts
+++ b/cypress/integration/all/app/activateuser.spec.ts
@@ -16,6 +16,10 @@ describe("User invite and activation", () => {
cy.findByLabelText(/email/i).click().type("ash@example.com");
+ cy.get(".create-user-form__new-user-radios").within(() => {
+ cy.findByRole("radio", { name: "Invite user" }).parent().click();
+ });
+
cy.findByRole("button", { name: /^create$/i }).click();
// Ensure the email has been delivered
diff --git a/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss b/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss
index 8574d7d392..8d1ac9e319 100644
--- a/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss
+++ b/frontend/components/TableContainer/DataTable/DropdownCell/_styles.scss
@@ -30,7 +30,7 @@
z-index: 6;
overflow: hidden;
border: 0;
- width: 150px;
+ width: 188px;
right: 28px;
left: unset;
top: unset;
diff --git a/frontend/components/forms/fields/Radio/Radio.tsx b/frontend/components/forms/fields/Radio/Radio.tsx
index be9d24be49..3410718406 100644
--- a/frontend/components/forms/fields/Radio/Radio.tsx
+++ b/frontend/components/forms/fields/Radio/Radio.tsx
@@ -27,8 +27,12 @@ const Radio = (props: IRadioProps): JSX.Element => {
} = props;
const wrapperClasses = classnames(baseClass, className);
+ const radioControlClass = classnames({
+ [`disabled`]: disabled,
+ });
+
return (
-
+
{
return `/v1/fleet/users/${id}/admin`;
},
diff --git a/frontend/fleet/entities/users.js b/frontend/fleet/entities/users.js
index 7a53072657..fbd96b4aac 100644
--- a/frontend/fleet/entities/users.js
+++ b/frontend/fleet/entities/users.js
@@ -11,6 +11,16 @@ export default (client) => {
.authenticatedPost(client._endpoint(USERS), JSON.stringify(formData))
.then((response) => helpers.addGravatarUrlToResource(response.user));
},
+ createUserWithoutInvitation: (formData) => {
+ const { USERS_ADMIN } = endpoints;
+
+ return client
+ .authenticatedPost(
+ client._endpoint(USERS_ADMIN),
+ JSON.stringify(formData)
+ )
+ .then((response) => helpers.addGravatarUrlToResource(response.user)); // TODO confirm
+ },
deleteSessions: (user) => {
const { USER_SESSIONS } = endpoints;
const endpoint = client._endpoint(USER_SESSIONS(user.id));
diff --git a/frontend/pages/admin/UserManagementPage/UserManagementPage.jsx b/frontend/pages/admin/UserManagementPage/UserManagementPage.jsx
index 3f6a552868..301f76463a 100644
--- a/frontend/pages/admin/UserManagementPage/UserManagementPage.jsx
+++ b/frontend/pages/admin/UserManagementPage/UserManagementPage.jsx
@@ -5,10 +5,8 @@ import { isEqual } from "lodash";
import { push } from "react-router-redux";
import memoize from "memoize-one";
-import Button from "components/buttons/Button";
import TableContainer from "components/TableContainer";
import Modal from "components/modals/Modal";
-import WarningBanner from "components/WarningBanner";
import inviteInterface from "interfaces/invite";
import configInterface from "interfaces/config";
import userInterface from "interfaces/user";
@@ -28,6 +26,7 @@ import { generateTableHeaders, combineDataSets } from "./UsersTableConfig";
import DeleteUserForm from "./components/DeleteUserForm";
import ResetPasswordModal from "./components/ResetPasswordModal";
import ResetSessionsModal from "./components/ResetSessionsModal";
+import { NewUserType } from "./components/UserForm/UserForm";
const baseClass = "user-management";
@@ -148,28 +147,53 @@ export class UserManagementPage extends Component {
onCreateUserSubmit = (formData) => {
const { dispatch, config } = this.props;
- // Do some data formatting adding `invited_by` for the request to be correct.
- const requestData = {
- ...formData,
- invited_by: formData.currentUserId,
- };
- delete requestData.currentUserId; // dont need this for the request.
- dispatch(inviteActions.create(requestData))
- .then(() => {
- dispatch(
- renderFlash(
- "success",
- `An invitation email was sent from ${config.sender_address} to ${formData.email}.`
- )
- );
- this.toggleCreateUserModal();
- })
- .catch(() => {
- dispatch(
- renderFlash("error", "Could not create user. Please try again.")
- );
- this.toggleCreateUserModal();
- });
+
+ if (formData.newUserType === NewUserType.AdminInvited) {
+ // Do some data formatting adding `invited_by` for the request to be correct and deleteing uncessary fields
+ const requestData = {
+ ...formData,
+ invited_by: formData.currentUserId,
+ };
+ delete requestData.currentUserId; // this field is not needed for the request
+ delete requestData.newUserType; // this field is not needed for the request
+ delete requestData.password; // this field is not needed for the request
+ dispatch(inviteActions.create(requestData))
+ .then(() => {
+ dispatch(
+ renderFlash(
+ "success",
+ `An invitation email was sent from ${config.sender_address} to ${formData.email}.`
+ )
+ );
+ this.toggleCreateUserModal();
+ })
+ .catch(() => {
+ dispatch(
+ renderFlash("error", "Could not create user. Please try again.")
+ );
+ this.toggleCreateUserModal();
+ });
+ } else {
+ // Do some data formatting deleteing uncessary fields
+ const requestData = {
+ ...formData,
+ };
+ delete requestData.currentUserId; // this field is not needed for the request
+ delete requestData.newUserType; // this field is not needed for the request
+ dispatch(userActions.createUserWithoutInvitation(requestData))
+ .then(() => {
+ dispatch(
+ renderFlash("success", `Successfully created ${requestData.name}.`)
+ );
+ this.toggleCreateUserModal();
+ })
+ .catch(() => {
+ dispatch(
+ renderFlash("error", "Could not create user. Please try again.")
+ );
+ this.toggleCreateUserModal();
+ });
+ }
};
onCreateCancel = (evt) => {
@@ -453,9 +477,11 @@ export class UserManagementPage extends Component {
availableTeams={teams}
defaultGlobalRole={"observer"}
defaultTeams={[]}
+ defaultNewUserType={false}
submitText={"Create"}
isBasicTier={isBasicTier}
- smtpConfigured={config.configured}
+ isSmtpConfigured={config.configured}
+ isNewUser
/>
);
@@ -514,34 +540,6 @@ export class UserManagementPage extends Component {
);
};
- renderSmtpWarning = () => {
- const { appConfigLoading, config } = this.props;
- const { goToAppConfigPage } = this;
-
- if (appConfigLoading) {
- return false;
- }
-
- return (
-
-
-
- SMTP is not currently configured in Fleet. The "Create
- User" feature requires that SMTP is configured in order to send
- invitation emails.
-
-
- Configure SMTP
-
-
-
- );
- };
-
render() {
const {
tableHeaders,
@@ -550,18 +548,11 @@ export class UserManagementPage extends Component {
renderDeleteUserModal,
renderResetPasswordModal,
renderResetSessionsModal,
- renderSmtpWarning,
toggleCreateUserModal,
onTableQueryChange,
onActionSelect,
} = this;
- const {
- config,
- loadingTableData,
- users,
- invites,
- currentUser,
- } = this.props;
+ const { loadingTableData, users, invites, currentUser } = this.props;
let tableData = [];
if (!loadingTableData) {
@@ -579,7 +570,6 @@ export class UserManagementPage extends Component {
Create new users, customize user permissions, and remove users from
Fleet.
- {renderSmtpWarning()}
{/* TODO: find a way to move these controls into the table component */}
);
diff --git a/frontend/pages/admin/UserManagementPage/UserManagementPage.tests.jsx b/frontend/pages/admin/UserManagementPage/UserManagementPage.tests.jsx
deleted file mode 100644
index 3ee54bf26c..0000000000
--- a/frontend/pages/admin/UserManagementPage/UserManagementPage.tests.jsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { mount } from "enzyme";
-
-import { connectedComponent, reduxMockStore } from "test/helpers";
-import ConnectedUserManagementPage from "pages/admin/UserManagementPage/UserManagementPage";
-import inviteActions from "redux/nodes/entities/invites/actions";
-import userActions from "redux/nodes/entities/users/actions";
-
-const currentUser = {
- email: "hi@gnar.dog",
- enabled: true,
- name: "Gnar Dog",
- position: "Head of Gnar",
- username: "gnardog",
- teams: [],
- global_role: "admin",
-};
-const store = {
- app: {
- config: {
- configured: true,
- },
- },
- auth: {
- user: {
- ...currentUser,
- },
- },
- entities: {
- users: {
- loading: false,
- data: {
- 1: {
- ...currentUser,
- },
- },
- originalOrder: [1],
- },
- invites: {
- loading: false,
- data: {
- 1: {
- global_role: "admin",
- email: "other@user.org",
- name: "Other user",
- teams: [],
- },
- },
- originalOrder: [1],
- },
- },
-};
-
-describe("UserManagementPage - component", () => {
- beforeEach(() => {
- jest
- .spyOn(userActions, "loadAll")
- .mockImplementation(() => () => Promise.resolve([]));
-
- jest
- .spyOn(inviteActions, "loadAll")
- .mockImplementation(() => () => Promise.resolve([]));
- });
-
- describe("rendering", () => {
- it('displays a disabled "Create user" button if email is not configured', () => {
- const notConfiguredStore = {
- ...store,
- app: { config: { configured: false } },
- };
- const notConfiguredMockStore = reduxMockStore(notConfiguredStore);
- const notConfiguredPage = mount(
- connectedComponent(ConnectedUserManagementPage, {
- mockStore: notConfiguredMockStore,
- })
- );
-
- const configuredStore = store;
- const configuredMockStore = reduxMockStore(configuredStore);
- const configuredPage = mount(
- connectedComponent(ConnectedUserManagementPage, {
- mockStore: configuredMockStore,
- })
- );
-
- expect(notConfiguredPage.find("Button").at(1).prop("disabled")).toEqual(
- true
- );
- expect(configuredPage.find("Button").first().prop("disabled")).toEqual(
- false
- );
- });
-
- it("displays a SmtpWarning if email is not configured", () => {
- const notConfiguredStore = {
- ...store,
- app: { config: { configured: false } },
- };
- const notConfiguredMockStore = reduxMockStore(notConfiguredStore);
- const notConfiguredPage = mount(
- connectedComponent(ConnectedUserManagementPage, {
- mockStore: notConfiguredMockStore,
- })
- );
-
- const configuredStore = store;
- const configuredMockStore = reduxMockStore(configuredStore);
- const configuredPage = mount(
- connectedComponent(ConnectedUserManagementPage, {
- mockStore: configuredMockStore,
- })
- );
-
- expect(notConfiguredPage.find("WarningBanner").html()).toBeTruthy();
- expect(configuredPage.find("WarningBanner").html()).toBeFalsy();
- });
- });
-
- it("goes to the app settings page for the user to resolve their smtp settings", () => {
- const notConfiguredStore = {
- ...store,
- app: { config: { configured: false } },
- };
- const mockStore = reduxMockStore(notConfiguredStore);
- const page = mount(
- connectedComponent(ConnectedUserManagementPage, { mockStore })
- );
-
- const smtpWarning = page.find("WarningBanner");
-
- smtpWarning.find("Button").simulate("click");
-
- const goToAppSettingsAction = {
- type: "@@router/CALL_HISTORY_METHOD",
- payload: { method: "push", args: ["/settings/organization"] },
- };
-
- expect(mockStore.getActions()).toContainEqual(goToAppSettingsAction);
- });
-});
diff --git a/frontend/pages/admin/UserManagementPage/UsersTableConfig.tsx b/frontend/pages/admin/UserManagementPage/UsersTableConfig.tsx
index fb794d1ba5..163c594072 100644
--- a/frontend/pages/admin/UserManagementPage/UsersTableConfig.tsx
+++ b/frontend/pages/admin/UserManagementPage/UsersTableConfig.tsx
@@ -209,7 +209,7 @@ const generateActionDropdownOptions = (
value: "delete",
},
];
- if (isCurrentUser || isSSOEnabled) {
+ if (isSSOEnabled) {
// remove "Require password reset" from dropdownOptions
dropdownOptions = dropdownOptions.filter(
(option) => option.label !== "Require password reset"
diff --git a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx
index 75d0207626..6c2ba41ca5 100644
--- a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx
+++ b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx
@@ -8,6 +8,12 @@ import validEmail from "components/forms/validators/valid_email";
// ignore TS error for now until these are rewritten in ts.
// @ts-ignore
+import validPassword from "components/forms/validators/valid_password";
+// @ts-ignore
+import IconToolTip from "components/IconToolTip";
+// @ts-ignore
+import InputField from "components/forms/fields/InputField";
+// @ts-ignore
import InputFieldWithIcon from "components/forms/fields/InputFieldWithIcon";
// @ts-ignore
import Checkbox from "components/forms/fields/Checkbox";
@@ -20,6 +26,11 @@ import OpenNewTabIcon from "../../../../../../assets/images/open-new-tab-12x12@2
const baseClass = "create-user-form";
+export enum NewUserType {
+ AdminInvited = "ADMIN_INVITED",
+ AdminCreated = "ADMIN_CREATED",
+}
+
enum UserTeamType {
GlobalUser = "GLOBAL_USER",
AssignTeams = "ASSIGN_TEAMS",
@@ -46,6 +57,8 @@ const globalUserRoles = [
export interface IFormData {
email: string;
name: string;
+ newUserType?: NewUserType | null;
+ password?: string | null;
sso_enabled: boolean;
global_role: string | null;
teams: ITeam[];
@@ -65,6 +78,8 @@ interface ICreateUserFormProps {
defaultGlobalRole?: string | null;
defaultTeams?: ITeam[];
isBasicTier: boolean;
+ isSmtpConfigured?: boolean;
+ isNewUser?: boolean;
validationErrors: any[]; // TODO: proper interface for validationErrors
smtpConfigured: boolean;
}
@@ -73,6 +88,7 @@ interface ICreateUserFormState {
errors: {
email: string | null;
name: string | null;
+ password: string | null;
sso_enabled: boolean | null;
};
formData: IFormData;
@@ -87,12 +103,17 @@ class UserForm extends Component {
errors: {
email: null,
name: null,
+ password: null,
sso_enabled: null,
},
formData: {
email: props.defaultEmail || "",
name: props.defaultName || "",
- sso_enabled: props.canUseSSO || false,
+ newUserType: props.isNewUser ? NewUserType.AdminCreated : null,
+ password: null,
+ sso_enabled: props.isNewUser
+ ? props.canUseSSO || false
+ : props.canUseSSO || false, // TODO revisit the else case for editing an existing user; shouldn't this be pulling from the user data instead of global app config?
global_role: props.defaultGlobalRole || null,
teams: props.defaultTeams || [],
currentUserId: props.currentUserId,
@@ -126,6 +147,12 @@ class UserForm extends Component {
};
};
+ onRadioChange = (formField: string): ((evt: string) => void) => {
+ return (evt: string) => {
+ return this.onInputChange(formField)(evt);
+ };
+ };
+
onIsGlobalUserChange = (value: string): void => {
const { formData } = this.state;
const isGlobalUser = value === UserTeamType.GlobalUser;
@@ -168,19 +195,39 @@ class UserForm extends Component {
}
};
+ // UserForm component can be used to create a new user or edit an existing user so submitData will be assembled accordingly
createSubmitData = (): IFormData => {
- const { currentUserId } = this.props;
+ const { currentUserId, isNewUser } = this.props;
const {
isGlobalUser,
- formData: { email, name, sso_enabled, global_role, teams },
+ formData: {
+ email,
+ name,
+ newUserType,
+ password,
+ sso_enabled,
+ global_role,
+ teams,
+ },
} = this.state;
const submitData = {
email,
name,
+ newUserType,
+ password,
sso_enabled,
currentUserId,
};
+
+ if (!isNewUser) {
+ delete submitData.newUserType; // this field will not be submitted when form is used to edit an existing user
+ }
+
+ if (submitData.sso_enabled || newUserType === NewUserType.AdminInvited) {
+ delete submitData.password; // this field will not be submitted with the form
+ }
+
return isGlobalUser
? { ...submitData, global_role, teams: [] }
: { ...submitData, global_role: null, teams };
@@ -189,8 +236,9 @@ class UserForm extends Component {
validate = (): boolean => {
const {
errors,
- formData: { email },
+ formData: { email, password, newUserType, sso_enabled },
} = this.state;
+ const { isNewUser } = this.props;
if (!validatePresence(email)) {
this.setState({
@@ -214,6 +262,29 @@ class UserForm extends Component {
return false;
}
+ if (isNewUser && newUserType === NewUserType.AdminCreated && !sso_enabled) {
+ if (!validatePresence(password)) {
+ this.setState({
+ errors: {
+ ...errors,
+ password: "Password field must be completed",
+ },
+ });
+
+ return false;
+ }
+ if (!validPassword(password)) {
+ this.setState({
+ errors: {
+ ...errors,
+ password: "Password must meet the criteria below",
+ },
+ });
+
+ return false;
+ }
+ }
+
return true;
};
@@ -288,14 +359,21 @@ class UserForm extends Component {
render(): JSX.Element {
const {
errors,
- formData: { email, name, sso_enabled },
+ formData: { email, name, newUserType, password, sso_enabled },
isGlobalUser,
} = this.state;
- const { onCancel, submitText, isBasicTier, smtpConfigured } = this.props;
+ const {
+ onCancel,
+ submitText,
+ isBasicTier,
+ isSmtpConfigured,
+ isNewUser,
+ } = this.props;
const {
onFormSubmit,
onInputChange,
onCheckboxChange,
+ onRadioChange,
onIsGlobalUserChange,
renderGlobalRoleForm,
renderTeamsForm,
@@ -323,7 +401,7 @@ class UserForm extends Component {
className="smtp-not-configured"
data-tip
data-for="smtp-tooltip"
- data-tip-disable={smtpConfigured}
+ data-tip-disable={isSmtpConfigured}
>
{
onChange={onInputChange("email")}
placeholder="Email"
value={email}
- disabled={!smtpConfigured}
+ disabled={!isSmtpConfigured}
/>
{
Password authentication will be disabled for this user.
+ {isNewUser && (
+
+
+
+
+
+
+
+ The Invite user feature requires that SMTP is
+
+ configured in order to send an invitation email.
+
+ SMTP can be configured in Organization settings.
+
+
+
+
+ {newUserType !== NewUserType.AdminInvited && !sso_enabled && (
+ <>
+
+
+
+
+
\
+ This password is temporary.
\
+ This user will be asked to set a new password after logging in to the Fleet UI.
\
+ This user will not be asked to set a new password after logging in to fleetctl or the Fleet API.
\
+ \
+ `}
+ />
+
+ >
+ )}
+
+ )}
{isBasicTier && (
diff --git a/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss b/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss
index 8f5e357c42..58f9806a10 100644
--- a/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss
+++ b/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss
@@ -1,4 +1,9 @@
.create-user-form {
+ &__new-user-container {
+ margin-top: $pad-large;
+ margin-bottom: $pad-large;
+ }
+
&__sso-input {
margin-top: $pad-large;
margin-bottom: $pad-large;
@@ -51,6 +56,15 @@
&__radio-input {
margin-bottom: $pad-medium;
+
+ &.disabled {
+ .radio__control {
+ background-color: $ui-fleet-black-25;
+ }
+ .radio__label {
+ color: $ui-fleet-black-25;
+ }
+ }
}
&__btn-wrap {
@@ -66,9 +80,38 @@
margin-bottom: 8px;
}
+ &__password {
+ width: 98%;
+ float: left;
+ padding: 0 $pad-medium 0 0;
+ box-sizing: border-box;
+
+ .input-icon-field {
+ width: 100%;
+ }
+ }
+
+ &__details {
+ float: right;
+ width: 2%;
+
+ .icon-tooltip {
+ margin-top: 12px;
+ }
+
+ .hint {
+ color: $core-fleet-black;
+
+ &--brand {
+ color: $core-vibrant-blue;
+ }
+ }
+ }
+
.sublabel {
margin: 0px;
}
+
&__tooltip-text {
width: 300px;
}
diff --git a/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.tests.ts b/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.tests.ts
index 2dab0530b3..c290778a73 100644
--- a/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.tests.ts
+++ b/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.tests.ts
@@ -1,5 +1,5 @@
import { userStub, userTeamStub } from "test/stubs";
-import { IFormData } from "../components/UserForm/UserForm";
+import { IFormData, NewUserType } from "../components/UserForm/UserForm";
import userManagementHelpers from "./userManagementHelpers";
describe("userManagementHelpers module", () => {
@@ -19,6 +19,7 @@ describe("userManagementHelpers module", () => {
email: "newemail@test.com",
sso_enabled: false,
name: "Gnar Mike",
+ newUserType: NewUserType.AdminCreated, // TODO revisit test
global_role: "admin",
teams: [updatedTeam, newTeam],
};
diff --git a/frontend/redux/nodes/entities/users/actions.js b/frontend/redux/nodes/entities/users/actions.js
index 65d995f95a..559f46e9a2 100644
--- a/frontend/redux/nodes/entities/users/actions.js
+++ b/frontend/redux/nodes/entities/users/actions.js
@@ -3,12 +3,17 @@ import Fleet from "fleet";
import config from "redux/nodes/entities/users/config";
import { formatErrorResponse } from "redux/nodes/entities/base/helpers";
import { logoutUser, updateUserSuccess } from "redux/nodes/auth/actions";
+import { create } from "lodash";
const { actions } = config;
// Actions for admin to require password reset for a user
export const REQUIRE_PASSWORD_RESET_SUCCESS = "REQUIRE_PASSWORD_RESET_SUCCESS";
export const REQUIRE_PASSWORD_RESET_FAILURE = "REQUIRE_PASSWORD_RESET_FAILURE";
+export const CREATE_USER_WITHOUT_INVITE_SUCCESS =
+ "CREATE_USER_WITHOUT_INVITE_SUCCESS";
+export const CREATE_USER_WITHOUT_INVITE_FAILURE =
+ "CREATE_USER_WITHOUT_INVITE_FAILURE";
export const requirePasswordResetSuccess = (user) => {
return {
@@ -24,6 +29,21 @@ export const requirePasswordResetFailure = (errors) => {
};
};
+// TODO does below need user
+export const createUserWithoutInviteSuccess = () => {
+ return {
+ type: CREATE_USER_WITHOUT_INVITE_SUCCESS,
+ payload: {},
+ };
+};
+
+export const createUserWithoutInviteFailure = (errors) => {
+ return {
+ type: CREATE_USER_WITHOUT_INVITE_FAILURE,
+ payload: { errors },
+ };
+};
+
export const changePassword = (
user,
{ new_password: newPassword, old_password: oldPassword }
@@ -77,6 +97,23 @@ export const confirmEmailChange = (user, token) => {
};
};
+export const createUserWithoutInvitation = (formData) => {
+ return (dispatch) => {
+ return Fleet.users
+ .createUserWithoutInvitation(formData)
+ .then((response) => {
+ return dispatch(createUserWithoutInviteSuccess(response));
+ })
+ .catch((response) => {
+ const errorsObject = formatErrorResponse(response);
+
+ dispatch(createUserWithoutInviteFailure(errorsObject));
+
+ throw errorsObject;
+ });
+ };
+};
+
export const deleteSessions = (user) => {
const { successAction, destroyFailure, destroySuccess } = actions;
@@ -156,6 +193,7 @@ export default {
...actions,
changePassword,
confirmEmailChange,
+ createUserWithoutInvitation,
enableUser,
requirePasswordReset,
deleteSessions,
diff --git a/frontend/redux/nodes/entities/users/reducer.js b/frontend/redux/nodes/entities/users/reducer.js
index c21398fe65..5d2ee15e67 100644
--- a/frontend/redux/nodes/entities/users/reducer.js
+++ b/frontend/redux/nodes/entities/users/reducer.js
@@ -1,6 +1,8 @@
import {
REQUIRE_PASSWORD_RESET_FAILURE,
REQUIRE_PASSWORD_RESET_SUCCESS,
+ CREATE_USER_WITHOUT_INVITE_FAILURE,
+ CREATE_USER_WITHOUT_INVITE_SUCCESS,
} from "./actions";
import config, { initialState } from "./config";
@@ -22,6 +24,22 @@ export default (state = initialState, { type, payload }) => {
loading: false,
errors: payload.errors,
};
+ case CREATE_USER_WITHOUT_INVITE_SUCCESS:
+ return {
+ ...state,
+ errors: {},
+ loading: false,
+ data: {
+ ...state.data,
+ },
+ };
+ case CREATE_USER_WITHOUT_INVITE_FAILURE:
+ return {
+ ...state,
+ loading: false,
+ errors: payload.errors,
+ };
+
default:
return config.reducer(state, { type, payload });
}