// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {adminPassword, adminUsername} from '@support/test_config'; import {getRandomId} from '@support/utils'; import {logError} from '../../../provision/log'; import client from './client'; import {getResponseFromError} from './common'; // **************************************************************** // Users // See https://api.mattermost.com/#tag/users // // Exported API function should have the following: // - documented using JSDoc // - meaningful description // - match the referenced API endpoints // - parameter/s defined by `@param` // - return value defined by `@return` // **************************************************************** /** * Login to Mattermost server as sysadmin. * @param {string} baseUrl - the base server URL * @return {Object} returns {user, status} on success or {error, status} on error */ export const apiAdminLogin = (baseUrl: string): any => { return apiLogin(baseUrl, { username: adminUsername, password: adminPassword, }); }; /** * Create a user. * See https://api.mattermost.com/#operation/CreateUser * @param {string} baseUrl - the base server URL * @param {string} option.prefix - prefix to email and username * @param {Object} option.user - user object to be created * @return {Object} returns {user} on success or {error, status} on error */ export const apiCreateUser = async (baseUrl: string, {prefix = 'user', user = null}: any = {}): Promise => { try { const newUser = user || generateRandomUser({prefix}); const response = await client.post( `${baseUrl}/api/v4/users`, newUser, ); return {user: {...response.data, password: newUser.password, newUser}}; } catch (err) { return getResponseFromError(err); } }; /** * Deactivate a user account. * See https://api.mattermost.com/#operation/DeleteUser * @param {string} baseUrl - the base server URL * @param {string} userId - the user ID * @return {Object} returns {status} on success or {error, status} on error */ export const apiDeactivateUser = async (baseUrl: string, userId: string): Promise => { try { const response = await client.delete(`${baseUrl}/api/v4/users/${userId}`); return {status: response.status}; } catch (err) { return getResponseFromError(err); } }; /** * Demote a user to a guest. * See https://api.mattermost.com/#operation/DemoteUserToGuest * @param {string} baseUrl - the base server URL * @param {string} userId - the user ID * @return {Object} returns {status} on success or {error, status} on error */ export const apiDemoteUserToGuest = async (baseUrl: string, userId: string): Promise => { try { const response = await client.post(`${baseUrl}/api/v4/users/${userId}/demote`); return {status: response.status}; } catch (err) { return getResponseFromError(err); } }; /** * Get user from a current session. * @param {string} baseUrl - the base server URL * @return {Object} returns {user} on success or {error, status} on error */ export const apiGetMe = (baseUrl: string): any => { return apiGetUserById(baseUrl, 'me'); }; /** * Get a user by ID. * See https://api.mattermost.com/#operation/GetUser * @param {string} baseUrl - the base server URL * @param {string} userId - the user ID * @return {Object} returns {user} on success or {error, status} on error */ export const apiGetUserById = async (baseUrl: string, userId: string): Promise => { try { const response = await client.get(`${baseUrl}/api/v4/users/${userId}`); return {user: response.data}; } catch (err) { return getResponseFromError(err); } }; /** * Get a user by username. * See https://api.mattermost.com/#operation/GetUserByUsername * @param {string} baseUrl - the base server URL * @param {string} username - the username * @return {Object} returns {user} on success or {error, status} on error */ export const apiGetUserByUsername = async (baseUrl: string, username: string): Promise => { try { const response = await client.get(`${baseUrl}/api/v4/users/username/${username}`); return {user: response.data}; } catch (err) { return getResponseFromError(err); } }; /** * Login to Mattermost server. * See https://api.mattermost.com/#operation/Login * @param {string} baseUrl - the base server URL * @param {string} user.username - username of a user * @param {string} user.password - password of a user * @return {Object} returns {user, status} on success or {error, status} on error */ export const apiLogin = async (baseUrl: string, user: any): Promise => { try { if (!user?.username || !user?.password) { // Every call site awaits this without checking the result, so a malformed // credential pair would otherwise surface only as a bare 400 in the log and // leave the shared client on its previous session. Name the caller's mistake. logError( '[apiLogin] refusing to log in with incomplete credentials ' + `(hasUsername=${Boolean(user?.username)}, hasPassword=${Boolean(user?.password)}). ` + 'Pass the user returned by apiCreateUser/apiInit, or its .newUser.', ); return {error: {message: 'apiLogin called with incomplete credentials'}, status: 0}; } const response = await client.post( `${baseUrl}/api/v4/users/login`, {login_id: user.username, password: user.password}, ); const {data, status} = response; return { status, user: data, }; } catch (err) { return getResponseFromError(err); } }; /** * Logout from the Mattermost server. * See https://api.mattermost.com/#operation/Logout * @param {string} baseUrl - the base server URL * @return {Object} returns {status} on success */ export const apiLogout = async (baseUrl: string): Promise => { const response = await client.post(`${baseUrl}/api/v4/users/logout`); return {status: response.status}; }; /** * Patch user from a current session. * @param {string} baseUrl - the base server URL * @param {Object} userData - data to partially update a user * @return {Object} returns {user} on success or {error, status} on error */ export const apiPatchMe = (baseUrl: string, userData: string): any => { return apiPatchUser(baseUrl, 'me', userData); }; /** * Patch a user. * See https://api.mattermost.com/#operation/PatchUser * Note: UserPatch does not include roles; use apiUpdateUserRoles to change roles. * @param {string} baseUrl - the base server URL * @param {string} userId - the user ID * @param {Object} userData - data to partially update a user * @return {Object} returns {user} on success or {error, status} on error */ export const apiPatchUser = async (baseUrl: string, userId: string, userData: any): Promise => { try { const response = await client.put( `${baseUrl}/api/v4/users/${userId}/patch`, userData, ); return {user: response.data}; } catch (err) { return getResponseFromError(err); } }; /** * Update a user's roles. * See https://api.mattermost.com/#operation/UpdateUserRoles * Roles must be a space-separated string (e.g. 'system_user system_admin'). * Caller must have PermissionManageRoles (e.g. system admin). * @param {string} baseUrl - the base server URL * @param {string} userId - the user ID * @param {string} roles - space-separated role names * @return {Object} returns {status} on success or {error, status} on error */ export const apiUpdateUserRoles = async (baseUrl: string, userId: string, roles: string): Promise => { try { const response = await client.put( `${baseUrl}/api/v4/users/${userId}/roles`, {roles}, ); return {status: response.status}; } catch (err) { return getResponseFromError(err); } }; /** * Update user active status. * See https://api.mattermost.com/#operation/UpdateUserActive * @param {string} baseUrl - the base server URL * @param {string} userId - the user ID * @param {boolean} active - use true to set the user active, false for inactive * @return {Object} returns {status} on success or {error, status} on error */ export const apiUpdateUserActiveStatus = async (baseUrl: string, userId: string, active: boolean): Promise => { try { const response = await client.put( `${baseUrl}/api/v4/users/${userId}/active`, {active}, ); return {status: response.status}; } catch (err) { return getResponseFromError(err); } }; type AutocompleteUser = {id: string}; type AutocompleteUsersResult = { users?: AutocompleteUser[]; out_of_channel?: AutocompleteUser[]; error?: {message?: string}; status?: number; }; /** * Autocomplete users in a team/channel. * See https://api.mattermost.com/#operation/AutocompleteUsers * @param {string} baseUrl - the base server URL * @param {string} option.teamId - team ID * @param {string} option.channelId - channel ID (populates out_of_channel) * @param {string} option.name - name prefix to match * @return {Object} returns autocomplete payload on success or {error, status} on error */ export const apiAutocompleteUsers = async ( baseUrl: string, {teamId, channelId, name}: {teamId: string; channelId?: string; name: string}, ): Promise => { try { const params = new URLSearchParams({in_team: teamId, name}); if (channelId) { params.set('in_channel', channelId); } const response = await client.get(`${baseUrl}/api/v4/users/autocomplete?${params.toString()}`); return response.data; } catch (err) { return getResponseFromError(err); } }; /** * Wait until a user appears in channel autocomplete out_of_channel results. * Fresh users can miss the first search until the server index catches up. */ export const waitForUserInAutocomplete = async ( baseUrl: string, { teamId, channelId, userId, name, timeoutMs = 30000, intervalMs = 1000, }: { teamId: string; channelId: string; userId: string; name: string; timeoutMs?: number; intervalMs?: number; }, ): Promise => { const deadline = Date.now() + timeoutMs; let lastError = ''; const poll = async (): Promise => { const result = await apiAutocompleteUsers(baseUrl, {teamId, channelId, name}); if ((result.out_of_channel ?? []).some((user) => user.id === userId)) { return; } lastError = result.error?.message || `HTTP ${result.status ?? 'ok'}; out_of_channel=${result.out_of_channel?.length ?? 0}`; if (Date.now() >= deadline) { throw new Error(`User ${userId} was not in out_of_channel autocomplete for "${name}" within ${timeoutMs}ms (${lastError}).`); } await new Promise((resolve) => { setTimeout(resolve, intervalMs); }); await poll(); }; await poll(); }; export const generateRandomUser = ({prefix = 'user', randomIdLength = 6} = {}) => { const randomId = getRandomId(randomIdLength); return { email: `${prefix}${randomId}@sample.mattermost.com`, username: `${prefix}${randomId}`, // 16-char password satisfies FIPS minimum length on cloud test servers. password: `P${randomId}!Test1234`, first_name: `F${randomId}`, last_name: `L${randomId}`, nickname: `N${randomId}`, position: `P${randomId}`, }; }; export const User = { apiAdminLogin, apiAutocompleteUsers, apiCreateUser, apiDeactivateUser, apiDemoteUserToGuest, apiGetMe, apiGetUserById, apiGetUserByUsername, apiLogin, apiLogout, apiPatchMe, apiPatchUser, apiUpdateUserActiveStatus, apiUpdateUserRoles, generateRandomUser, waitForUserInAutocomplete, }; export default User;