fleet/frontend/services/entities/users.ts
Nico 578f35292c
[Frontend] Create API-only users that only have access to customer-defined Fleet API endpoints (#43281)
**Related issue:** Resolves #42879

* Full UI for API-only user management: create/edit flows, fleet/role
assignment, selectable API endpoint permissions, and one-time API key
display.
* New reusable components: API user form, endpoint selector, API access
section, and API key presentation.
* Admin workflow switched from in-page modals to dedicated pages and
streamlined action dropdown navigation.
* Layout and styling refinements for user management, team lists, and
dropdown behaviors.

---------

Co-authored-by: Juan Fernandez <juan@fleetdm.com>
2026-04-20 09:18:02 -04:00

214 lines
6.1 KiB
TypeScript

/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import sendRequest from "services";
import endpoints from "utilities/endpoints";
import helpers from "utilities/helpers";
import { buildQueryStringFromParams } from "utilities/url";
import {
ICreateUserFormData,
IResetPasswordForm,
IUpdateUserFormData,
IUser,
ICreateUserWithInvitationFormData,
} from "interfaces/user";
import { ITeamSummary, INewTeamUser } from "interfaces/team";
import { IApiEndpointRef } from "interfaces/api_endpoint";
import type { IRegistrationFormData } from "interfaces/registration_form_data";
import { IUserSettings } from "interfaces/config";
export interface ISortOption {
id: number;
desc: boolean;
}
interface IUserSearchOptions {
page?: number;
perPage?: number;
globalFilter?: string;
sortBy?: ISortOption[];
teamId?: number;
}
interface IForgotPassword {
email: string;
}
interface IUpdatePassword {
new_password: string;
old_password: string;
}
interface IRequirePasswordReset {
require: boolean;
}
export interface IGetMeResponse {
user: IUser;
available_teams: ITeamSummary[];
settings: IUserSettings;
}
export default {
changePassword: (passwordParams: IUpdatePassword) => {
const { CHANGE_PASSWORD } = endpoints;
return sendRequest("POST", CHANGE_PASSWORD, passwordParams);
},
confirmEmailChange: (currentUser: IUser, token: string) => {
const { CONFIRM_EMAIL_CHANGE } = endpoints;
return sendRequest("GET", CONFIRM_EMAIL_CHANGE(token)).then((response) => {
return { ...currentUser, email: response.new_email };
});
},
create: (formData: ICreateUserWithInvitationFormData) => {
const { USERS } = endpoints;
return sendRequest("POST", USERS, formData).then((response) =>
helpers.addGravatarUrlToResource(response.user)
);
},
createUserWithoutInvitation: (
formData: ICreateUserFormData
): Promise<{ user: IUser; token?: string }> => {
const { USERS_ADMIN } = endpoints;
return sendRequest("POST", USERS_ADMIN, formData).then((response) => ({
user: helpers.addGravatarUrlToResource(response.user),
token: response.token,
}));
},
createApiOnlyUser: (formData: {
name: string;
global_role?: string | null;
fleets?: INewTeamUser[];
api_endpoints?: IApiEndpointRef[] | null;
}): Promise<{ user: IUser; token?: string }> => {
const { USERS_API_ONLY } = endpoints;
return sendRequest("POST", USERS_API_ONLY, formData).then((response) => ({
user: response.user,
token: response.token,
}));
},
updateApiOnlyUser: (
userId: number,
formData: Record<string, unknown>
): Promise<IUser> => {
const { USERS_API_ONLY } = endpoints;
const path = `${USERS_API_ONLY}/${userId}`;
return sendRequest("PATCH", path, formData).then(
(response) => response.user
);
},
getUserById: (userId: number): Promise<IUser> => {
const { USERS } = endpoints;
const path = `${USERS}/${userId}`;
return sendRequest("GET", path).then((response) =>
helpers.addGravatarUrlToResource(response.user)
);
},
deleteSessions: (userId: number) => {
const { USER_SESSIONS } = endpoints;
const path = USER_SESSIONS(userId);
return sendRequest("DELETE", path);
},
destroy: (userId: number) => {
const { USERS } = endpoints;
const path = `${USERS}/${userId}`;
return sendRequest("DELETE", path);
},
enable: (user: IUser, enabled: boolean) => {
const { ENABLE_USER } = endpoints;
return sendRequest("POST", ENABLE_USER(user.id), {
enabled,
}).then((response) => helpers.addGravatarUrlToResource(response.user));
},
forgotPassword: ({ email }: IForgotPassword) => {
const { FORGOT_PASSWORD } = endpoints;
return sendRequest("POST", FORGOT_PASSWORD, { email });
},
loadAll: ({ globalFilter = "", teamId }: IUserSearchOptions = {}) => {
const queryParams = {
query: globalFilter,
fleet_id: teamId,
};
const queryString = buildQueryStringFromParams(queryParams);
const endpoint = endpoints.USERS;
const path = `${endpoint}?${queryString}`;
return sendRequest("GET", path).then((response) => {
const { users } = response;
return users.map((u: IUser) => helpers.addGravatarUrlToResource(u));
});
},
me: (): Promise<IGetMeResponse> => {
// include the user's settings when calling from the UI
const path = `${endpoints.ME}?include_ui_settings=true`;
return sendRequest("GET", path).then(
({ user, available_teams, settings }) => {
return {
user: helpers.addGravatarUrlToResource(user),
available_teams,
settings,
};
}
);
},
performRequiredPasswordReset: (new_password: string) => {
const { PERFORM_REQUIRED_PASSWORD_RESET } = endpoints;
return sendRequest("POST", PERFORM_REQUIRED_PASSWORD_RESET, {
new_password,
}).then((response) => helpers.addGravatarUrlToResource(response.user));
},
requirePasswordReset: (
userId: number,
{ require }: IRequirePasswordReset
) => {
const { USERS } = endpoints;
const path = `${USERS}/${userId}/require_password_reset`;
return sendRequest("POST", path, { require }).then((response) =>
helpers.addGravatarUrlToResource(response.user)
);
},
resetPassword: (
formData: IResetPasswordForm & { password_reset_token: string }
) => {
const { RESET_PASSWORD } = endpoints;
return sendRequest("POST", RESET_PASSWORD, formData);
},
setup: (formData: IRegistrationFormData) => {
const { SETUP } = endpoints;
const setupData = helpers.setupData(formData);
return sendRequest("POST", SETUP, setupData);
},
update: (userId: number, formData: IUpdateUserFormData) => {
const { USERS } = endpoints;
const path = `${USERS}/${userId}`;
return sendRequest("PATCH", path, formData).then((response) =>
helpers.addGravatarUrlToResource(response.user)
);
},
updateAdmin: (user: IUser, admin: boolean) => {
const { UPDATE_USER_ADMIN } = endpoints;
return sendRequest(
"POST",
UPDATE_USER_ADMIN(user.id),
admin
).then((response) => helpers.addGravatarUrlToResource(response.user));
},
};