From cc9692257846d912bf52d98a68bbf49cc78d0677 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 4 Mar 2024 16:33:42 -0300 Subject: [PATCH 01/18] Convert saveUser file to ts --- .../functions/{saveUser.js => saveUser.ts} | 86 ++++++++++--------- .../lib/server/methods/insertOrUpdateUser.ts | 2 +- 2 files changed, 45 insertions(+), 43 deletions(-) rename apps/meteor/app/lib/server/functions/{saveUser.js => saveUser.ts} (82%) diff --git a/apps/meteor/app/lib/server/functions/saveUser.js b/apps/meteor/app/lib/server/functions/saveUser.ts similarity index 82% rename from apps/meteor/app/lib/server/functions/saveUser.js rename to apps/meteor/app/lib/server/functions/saveUser.ts index ef6a7e9fe7bd..6c4ae3961e9b 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.js +++ b/apps/meteor/app/lib/server/functions/saveUser.ts @@ -1,9 +1,11 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; import { isUserFederated } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; +import type { UserCreateParamsPOST, UsersUpdateParamsPOST } from '@rocket.chat/rest-typings'; import Gravatar from 'gravatar'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; +import type { UpdateFilter } from 'mongodb'; import _ from 'underscore'; import { callbacks } from '../../../../lib/callbacks'; @@ -25,6 +27,10 @@ import { setEmail } from './setEmail'; import { setStatusText } from './setStatusText'; import { setUserAvatar } from './setUserAvatar'; +type ICreateUserParams = UserCreateParamsPOST; +type IUpdateUserParams = UsersUpdateParamsPOST['data'] & { _id: IUser['_id'] }; +export type ISaveUserDataParams = ICreateUserParams | IUpdateUserParams; + const MAX_BIO_LENGTH = 260; const MAX_NICKNAME_LENGTH = 120; @@ -40,25 +46,22 @@ Meteor.startup(() => { }); }); -async function _sendUserEmail(subject, html, userData) { +async function _sendUserEmail(subject: string, html: string, userData: RequiredField) { const email = { to: userData.email, - from: settings.get('From_Email'), + from: settings.get('From_Email'), subject, html, data: { email: userData.email, password: userData.password, + ...(userData.name && { name: userData.name }), }, }; - if (typeof userData.name !== 'undefined') { - email.data.name = userData.name; - } - try { await Mailer.send(email); - } catch (error) { + } catch (error: any) { throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${error.message}`, { function: 'RocketChat.saveUser', message: error.message, @@ -66,8 +69,9 @@ async function _sendUserEmail(subject, html, userData) { } } -async function validateUserData(userId, userData) { +async function validateUserData(userId: IUser['_id'], userData: ISaveUserDataParams) { const existingRoles = _.pluck(await getRoles(), '_id'); + const isUpdateUserParams = '_id' in userData; if (userData.verified && userData._id && userId === userData._id) { throw new Meteor.Error('error-action-not-allowed', 'Editing email verification is not allowed', { @@ -83,7 +87,7 @@ async function validateUserData(userId, userData) { }); } - if (!userData._id && !(await hasPermissionAsync(userId, 'create-user'))) { + if (!isUpdateUserParams && !(await hasPermissionAsync(userId, 'create-user'))) { throw new Meteor.Error('error-action-not-allowed', 'Adding user is not allowed', { method: 'insertOrUpdateUser', action: 'Adding_user', @@ -97,21 +101,21 @@ async function validateUserData(userId, userData) { }); } - if (userData.roles && userData.roles.includes('admin') && !(await hasPermissionAsync(userId, 'assign-admin-role'))) { + if (userData.roles?.includes('admin') && !(await hasPermissionAsync(userId, 'assign-admin-role'))) { throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', { method: 'insertOrUpdateUser', action: 'Assign_admin', }); } - if (settings.get('Accounts_RequireNameForSignUp') && !userData._id && !trim(userData.name)) { + if (settings.get('Accounts_RequireNameForSignUp') && !isUpdateUserParams && !trim(userData.name)) { throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { method: 'insertOrUpdateUser', field: 'Name', }); } - if (!userData._id && !trim(userData.username)) { + if (!isUpdateUserParams && !trim(userData.username)) { throw new Meteor.Error('error-the-field-is-required', 'The field Username is required', { method: 'insertOrUpdateUser', field: 'Username', @@ -134,14 +138,14 @@ async function validateUserData(userId, userData) { }); } - if (!userData._id && !userData.password && !userData.setRandomPassword) { + if (!isUpdateUserParams && !userData.password && !userData.setRandomPassword) { throw new Meteor.Error('error-the-field-is-required', 'The field Password is required', { method: 'insertOrUpdateUser', field: 'Password', }); } - if (!userData._id) { + if (!isUpdateUserParams) { if (!(await checkUsernameAvailability(userData.username))) { throw new Meteor.Error('error-field-unavailable', `${_.escape(userData.username)} is already in use :(`, { method: 'insertOrUpdateUser', @@ -158,22 +162,20 @@ async function validateUserData(userId, userData) { } } -/** - * Validate permissions to edit user fields - * - * @param {string} userId - * @param {{ _id: string, roles?: string[], username?: string, name?: string, statusText?: string, email?: string, password?: string}} userData - */ -export async function validateUserEditing(userId, userData) { +export async function validateUserEditing(userId: IUser['_id'], userData: IUpdateUserParams) { const editingMyself = userData._id && userId === userData._id; const canEditOtherUserInfo = await hasPermissionAsync(userId, 'edit-other-user-info'); const canEditOtherUserPassword = await hasPermissionAsync(userId, 'edit-other-user-password'); const user = await Users.findOneById(userData._id); - const isEditingUserRoles = (previousRoles, newRoles) => + if (!user) { + throw new Error('error-invalid-user'); + } + + const isEditingUserRoles = (previousRoles: IUser['roles'], newRoles?: IUser['roles']) => typeof newRoles !== 'undefined' && !_.isEqual(_.sortBy(previousRoles), _.sortBy(newRoles)); - const isEditingField = (previousValue, newValue) => typeof newValue !== 'undefined' && newValue !== previousValue; + const isEditingField = (previousValue?: string, newValue?: string) => typeof newValue !== 'undefined' && newValue !== previousValue; if (isEditingUserRoles(user.roles, userData.roles) && !(await hasPermissionAsync(userId, 'assign-roles'))) { throw new Meteor.Error('error-action-not-allowed', 'Assign roles is not allowed', { @@ -242,8 +244,8 @@ export async function validateUserEditing(userId, userData) { } } -const handleBio = (updateUser, bio) => { - if (bio && bio.trim()) { +const handleBio = (updateUser: DeepWritable>, bio?: string) => { + if (bio?.trim()) { if (bio.length > MAX_BIO_LENGTH) { throw new Meteor.Error('error-bio-size-exceeded', `Bio size exceeds ${MAX_BIO_LENGTH} characters`, { method: 'saveUserProfile', @@ -257,8 +259,8 @@ const handleBio = (updateUser, bio) => { } }; -const handleNickname = (updateUser, nickname) => { - if (nickname && nickname.trim()) { +const handleNickname = (updateUser: DeepWritable>, nickname?: string) => { + if (nickname?.trim()) { if (nickname.length > MAX_NICKNAME_LENGTH) { throw new Meteor.Error('error-nickname-size-exceeded', `Nickname size exceeds ${MAX_NICKNAME_LENGTH} characters`, { method: 'saveUserProfile', @@ -272,7 +274,7 @@ const handleNickname = (updateUser, nickname) => { } }; -const saveNewUser = async function (userData, sendPassword) { +const saveNewUser = async function (userData: ICreateUserParams, sendPassword: boolean) { await validateEmailDomain(userData.email); const roles = (!!userData.roles && userData.roles.length > 0 && userData.roles) || getNewUserRoles(); @@ -283,13 +285,11 @@ const saveNewUser = async function (userData, sendPassword) { username: userData.username, password: userData.password, joinDefaultChannels: userData.joinDefaultChannels, + ...(userData.email && { email: userData.email }), isGuest, globalRoles: roles, skipNewUserRolesSetting: true, }; - if (userData.email) { - createUser.email = userData.email; - } const _id = await Accounts.createUserAsync(createUser); @@ -321,8 +321,6 @@ const saveNewUser = async function (userData, sendPassword) { await _sendUserEmail(settings.get('Password_Changed_Email_Subject'), passwordChangedHtml, userData); } - userData._id = _id; - if (settings.get('Accounts_SetDefaultAvatar') === true && userData.email) { const gravatarUrl = Gravatar.url(userData.email, { default: '404', @@ -331,7 +329,7 @@ const saveNewUser = async function (userData, sendPassword) { }); try { - await setUserAvatar(userData, gravatarUrl, '', 'url'); + await setUserAvatar({ _id, username: userData.username }, gravatarUrl, '', 'url'); } catch (e) { // Ignore this error for now, as it not being successful isn't bad } @@ -342,8 +340,8 @@ const saveNewUser = async function (userData, sendPassword) { return _id; }; -export const saveUser = async function (userId, userData) { - const oldUserData = userData._id && (await Users.findOneById(userData._id)); +export const saveUser = async function (userId: IUser['_id'], userData: ISaveUserDataParams) { + const oldUserData = '_id' in userData && (await Users.findOneById(userData._id)); if (oldUserData && isUserFederated(oldUserData)) { throw new Meteor.Error('Edit_Federated_User_Not_Allowed', 'Not possible to edit a federated user'); } @@ -367,7 +365,7 @@ export const saveUser = async function (userId, userData) { delete userData.setRandomPassword; } - if (!userData._id) { + if (!('_id' in userData)) { return saveNewUser(userData, sendPassword); } @@ -399,8 +397,7 @@ export const saveUser = async function (userId, userData) { } if ( - userData.password && - userData.password.trim() && + userData.password?.trim() && (await hasPermissionAsync(userId, 'edit-other-user-password')) && passwordPolicy.validate(userData.password) ) { @@ -421,7 +418,7 @@ export const saveUser = async function (userId, userData) { updateUser.$set.roles = userData.roles; } if (userData.settings) { - updateUser.$set.settings = { preferences: userData.settings.preferences }; + updateUser.$set['settings.preferences'] = userData.settings.preferences; } if (userData.language) { @@ -455,8 +452,13 @@ export const saveUser = async function (userId, userData) { performedBy: await safeGetMeteorUser(), }); - if (sendPassword) { - await _sendUserEmail(settings.get('Password_Changed_Email_Subject'), passwordChangedHtml, userData); + if (sendPassword && userUpdated?.emails && userUpdated.emails[0].address) { + userData.email = userUpdated.emails[0].address; + await _sendUserEmail( + settings.get('Password_Changed_Email_Subject'), + passwordChangedHtml, + userData as RequiredField, + ); } if (typeof userData.verified === 'boolean') { diff --git a/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts b/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts index 122b11172d57..7f606036c3c6 100644 --- a/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts +++ b/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts @@ -25,6 +25,6 @@ Meteor.methods({ }); } - return saveUser(Meteor.userId(), userData); + return saveUser(Meteor.userId(), userData as ISaveUserDataParams); }), }); From fd79ce500fea3c5594bd7f2c802f65466232ab7e Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 4 Mar 2024 16:35:25 -0300 Subject: [PATCH 02/18] Do not allow unused joinDefaultChannels param in users.update --- packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts index 4814d25874df..92dd6de513fd 100644 --- a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts @@ -16,7 +16,6 @@ export type UsersUpdateParamsPOST = { nickname?: string; statusText?: string; roles?: string[]; - joinDefaultChannels?: boolean; requirePasswordChange?: boolean; setRandomPassword?: boolean; sendWelcomeEmail?: boolean; @@ -78,10 +77,6 @@ const UsersUpdateParamsPostSchema = { }, nullable: true, }, - joinDefaultChannels: { - type: 'boolean', - nullable: true, - }, requirePasswordChange: { type: 'boolean', nullable: true, From abdd36ac0db971edfe2ce36b1db8fdbd08aaaae3 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 4 Mar 2024 16:36:10 -0300 Subject: [PATCH 03/18] Do not allow user creation on users.update endpoint --- packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts index 92dd6de513fd..a71f35666226 100644 --- a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts @@ -31,6 +31,7 @@ const UsersUpdateParamsPostSchema = { properties: { userId: { type: 'string', + minLength: 1, }, confirmRelinquish: { type: 'boolean', From 798c14e785b56d7bb19be43b07b455726250a246 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 4 Mar 2024 16:37:47 -0300 Subject: [PATCH 04/18] Add settings field to users.update and users.create (required by typecheck) --- .../rest-typings/src/v1/users/UserCreateParamsPOST.ts | 9 +++++++++ .../rest-typings/src/v1/users/UsersUpdateParamsPOST.ts | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts b/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts index 49fb8b2f6912..d257e1b86ce9 100644 --- a/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts @@ -1,3 +1,4 @@ +import type { IUserSettings } from '@rocket.chat/core-typings'; import Ajv from 'ajv'; const ajv = new Ajv({ @@ -42,6 +43,14 @@ const userCreateParamsPostSchema = { sendWelcomeEmail: { type: 'boolean', nullable: true }, verified: { type: 'boolean', nullable: true }, customFields: { type: 'object' }, + settings: { + type: 'object', + properties: { + profile: { type: 'object' }, + preferences: { type: 'object' }, + }, + additionalProperties: false, + }, fields: { type: 'string', nullable: true }, }, additionalProperties: false, diff --git a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts index a71f35666226..2d29fe56ca82 100644 --- a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts @@ -22,6 +22,7 @@ export type UsersUpdateParamsPOST = { verified?: boolean; customFields?: Record; status?: string; + settings?: { preferences?: Record }; }; confirmRelinquish?: boolean; }; @@ -102,6 +103,14 @@ const UsersUpdateParamsPostSchema = { type: 'string', nullable: true, }, + settings: { + type: 'object', + nullable: true, + properties: { + preferences: { type: 'object' }, + additionalProperties: false, + }, + }, }, required: [], additionalProperties: false, From e0f0ca8178a2be7fd3dd75e6c9d85df748397c96 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 4 Mar 2024 16:38:30 -0300 Subject: [PATCH 05/18] Fix IUserSettings type to mirror what's accepted and set through the API --- packages/core-typings/src/IUser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts index 76d31704bc6e..63f23c2ed5bb 100644 --- a/packages/core-typings/src/IUser.ts +++ b/packages/core-typings/src/IUser.ts @@ -138,7 +138,7 @@ export interface IUserEmail { } export interface IUserSettings { - profile: any; + profile?: any; preferences?: { [key: string]: any; }; From a464c9f8acaef1342995bfd102d775db9084efae Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 5 Mar 2024 09:56:58 -0300 Subject: [PATCH 06/18] Add language field to users.update (required by typecheck) --- packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts index 2d29fe56ca82..af17b7d49859 100644 --- a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts @@ -23,6 +23,7 @@ export type UsersUpdateParamsPOST = { customFields?: Record; status?: string; settings?: { preferences?: Record }; + language?: string; }; confirmRelinquish?: boolean; }; @@ -111,6 +112,10 @@ const UsersUpdateParamsPostSchema = { additionalProperties: false, }, }, + language: { + type: 'string', + nullable: true, + }, }, required: [], additionalProperties: false, From 32e70d5830aad1f24f20e04e04e4e31c5643e365 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:41:33 -0300 Subject: [PATCH 07/18] Create changeset --- .changeset/four-snakes-deny.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/four-snakes-deny.md diff --git a/.changeset/four-snakes-deny.md b/.changeset/four-snakes-deny.md new file mode 100644 index 000000000000..4215942b0ff3 --- /dev/null +++ b/.changeset/four-snakes-deny.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/core-typings": patch +"@rocket.chat/rest-typings": patch +--- + +Removed upsert behavior on `users.update` endpoint (`joinDefaultChannels` param or empty `userId` are not allowed anymore) From 23807fb827b4943d789bf2a878516ed5e302e4e7 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:44:39 -0300 Subject: [PATCH 08/18] Fix change type --- .changeset/four-snakes-deny.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/four-snakes-deny.md b/.changeset/four-snakes-deny.md index 4215942b0ff3..0d5e2ca10b2a 100644 --- a/.changeset/four-snakes-deny.md +++ b/.changeset/four-snakes-deny.md @@ -1,7 +1,7 @@ --- -"@rocket.chat/meteor": patch -"@rocket.chat/core-typings": patch -"@rocket.chat/rest-typings": patch +"@rocket.chat/meteor": major +"@rocket.chat/core-typings": major +"@rocket.chat/rest-typings": major --- Removed upsert behavior on `users.update` endpoint (`joinDefaultChannels` param or empty `userId` are not allowed anymore) From 632f151d1fc8168f55156db4a7385aa8029c5838 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Thu, 7 Mar 2024 12:54:42 -0300 Subject: [PATCH 09/18] Do not support new params in user.update and users.create --- apps/meteor/app/lib/server/functions/saveUser.ts | 7 ------- .../src/v1/users/UserCreateParamsPOST.ts | 9 --------- .../src/v1/users/UsersUpdateParamsPOST.ts | 14 -------------- 3 files changed, 30 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/saveUser.ts b/apps/meteor/app/lib/server/functions/saveUser.ts index 6c4ae3961e9b..afb99ec2c90c 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.ts +++ b/apps/meteor/app/lib/server/functions/saveUser.ts @@ -417,13 +417,6 @@ export const saveUser = async function (userId: IUser['_id'], userData: ISaveUse if (userData.roles) { updateUser.$set.roles = userData.roles; } - if (userData.settings) { - updateUser.$set['settings.preferences'] = userData.settings.preferences; - } - - if (userData.language) { - updateUser.$set.language = userData.language; - } if (typeof userData.requirePasswordChange !== 'undefined') { updateUser.$set.requirePasswordChange = userData.requirePasswordChange; diff --git a/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts b/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts index d257e1b86ce9..49fb8b2f6912 100644 --- a/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UserCreateParamsPOST.ts @@ -1,4 +1,3 @@ -import type { IUserSettings } from '@rocket.chat/core-typings'; import Ajv from 'ajv'; const ajv = new Ajv({ @@ -43,14 +42,6 @@ const userCreateParamsPostSchema = { sendWelcomeEmail: { type: 'boolean', nullable: true }, verified: { type: 'boolean', nullable: true }, customFields: { type: 'object' }, - settings: { - type: 'object', - properties: { - profile: { type: 'object' }, - preferences: { type: 'object' }, - }, - additionalProperties: false, - }, fields: { type: 'string', nullable: true }, }, additionalProperties: false, diff --git a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts index af17b7d49859..a71f35666226 100644 --- a/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersUpdateParamsPOST.ts @@ -22,8 +22,6 @@ export type UsersUpdateParamsPOST = { verified?: boolean; customFields?: Record; status?: string; - settings?: { preferences?: Record }; - language?: string; }; confirmRelinquish?: boolean; }; @@ -104,18 +102,6 @@ const UsersUpdateParamsPostSchema = { type: 'string', nullable: true, }, - settings: { - type: 'object', - nullable: true, - properties: { - preferences: { type: 'object' }, - additionalProperties: false, - }, - }, - language: { - type: 'string', - nullable: true, - }, }, required: [], additionalProperties: false, From e2e9838e2d8f20fca46aab2bc638a1a75a6296a8 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 11 Mar 2024 11:53:00 -0300 Subject: [PATCH 10/18] Undo unrelated changes --- .../functions/{saveUser.ts => saveUser.js} | 91 ++++++++++--------- .../lib/server/methods/insertOrUpdateUser.ts | 2 +- packages/core-typings/src/IUser.ts | 2 +- 3 files changed, 50 insertions(+), 45 deletions(-) rename apps/meteor/app/lib/server/functions/{saveUser.ts => saveUser.js} (83%) diff --git a/apps/meteor/app/lib/server/functions/saveUser.ts b/apps/meteor/app/lib/server/functions/saveUser.js similarity index 83% rename from apps/meteor/app/lib/server/functions/saveUser.ts rename to apps/meteor/app/lib/server/functions/saveUser.js index afb99ec2c90c..ef6a7e9fe7bd 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.ts +++ b/apps/meteor/app/lib/server/functions/saveUser.js @@ -1,11 +1,9 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; import { isUserFederated } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; -import type { UserCreateParamsPOST, UsersUpdateParamsPOST } from '@rocket.chat/rest-typings'; import Gravatar from 'gravatar'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; -import type { UpdateFilter } from 'mongodb'; import _ from 'underscore'; import { callbacks } from '../../../../lib/callbacks'; @@ -27,10 +25,6 @@ import { setEmail } from './setEmail'; import { setStatusText } from './setStatusText'; import { setUserAvatar } from './setUserAvatar'; -type ICreateUserParams = UserCreateParamsPOST; -type IUpdateUserParams = UsersUpdateParamsPOST['data'] & { _id: IUser['_id'] }; -export type ISaveUserDataParams = ICreateUserParams | IUpdateUserParams; - const MAX_BIO_LENGTH = 260; const MAX_NICKNAME_LENGTH = 120; @@ -46,22 +40,25 @@ Meteor.startup(() => { }); }); -async function _sendUserEmail(subject: string, html: string, userData: RequiredField) { +async function _sendUserEmail(subject, html, userData) { const email = { to: userData.email, - from: settings.get('From_Email'), + from: settings.get('From_Email'), subject, html, data: { email: userData.email, password: userData.password, - ...(userData.name && { name: userData.name }), }, }; + if (typeof userData.name !== 'undefined') { + email.data.name = userData.name; + } + try { await Mailer.send(email); - } catch (error: any) { + } catch (error) { throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${error.message}`, { function: 'RocketChat.saveUser', message: error.message, @@ -69,9 +66,8 @@ async function _sendUserEmail(subject: string, html: string, userData: RequiredF } } -async function validateUserData(userId: IUser['_id'], userData: ISaveUserDataParams) { +async function validateUserData(userId, userData) { const existingRoles = _.pluck(await getRoles(), '_id'); - const isUpdateUserParams = '_id' in userData; if (userData.verified && userData._id && userId === userData._id) { throw new Meteor.Error('error-action-not-allowed', 'Editing email verification is not allowed', { @@ -87,7 +83,7 @@ async function validateUserData(userId: IUser['_id'], userData: ISaveUserDataPar }); } - if (!isUpdateUserParams && !(await hasPermissionAsync(userId, 'create-user'))) { + if (!userData._id && !(await hasPermissionAsync(userId, 'create-user'))) { throw new Meteor.Error('error-action-not-allowed', 'Adding user is not allowed', { method: 'insertOrUpdateUser', action: 'Adding_user', @@ -101,21 +97,21 @@ async function validateUserData(userId: IUser['_id'], userData: ISaveUserDataPar }); } - if (userData.roles?.includes('admin') && !(await hasPermissionAsync(userId, 'assign-admin-role'))) { + if (userData.roles && userData.roles.includes('admin') && !(await hasPermissionAsync(userId, 'assign-admin-role'))) { throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', { method: 'insertOrUpdateUser', action: 'Assign_admin', }); } - if (settings.get('Accounts_RequireNameForSignUp') && !isUpdateUserParams && !trim(userData.name)) { + if (settings.get('Accounts_RequireNameForSignUp') && !userData._id && !trim(userData.name)) { throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { method: 'insertOrUpdateUser', field: 'Name', }); } - if (!isUpdateUserParams && !trim(userData.username)) { + if (!userData._id && !trim(userData.username)) { throw new Meteor.Error('error-the-field-is-required', 'The field Username is required', { method: 'insertOrUpdateUser', field: 'Username', @@ -138,14 +134,14 @@ async function validateUserData(userId: IUser['_id'], userData: ISaveUserDataPar }); } - if (!isUpdateUserParams && !userData.password && !userData.setRandomPassword) { + if (!userData._id && !userData.password && !userData.setRandomPassword) { throw new Meteor.Error('error-the-field-is-required', 'The field Password is required', { method: 'insertOrUpdateUser', field: 'Password', }); } - if (!isUpdateUserParams) { + if (!userData._id) { if (!(await checkUsernameAvailability(userData.username))) { throw new Meteor.Error('error-field-unavailable', `${_.escape(userData.username)} is already in use :(`, { method: 'insertOrUpdateUser', @@ -162,20 +158,22 @@ async function validateUserData(userId: IUser['_id'], userData: ISaveUserDataPar } } -export async function validateUserEditing(userId: IUser['_id'], userData: IUpdateUserParams) { +/** + * Validate permissions to edit user fields + * + * @param {string} userId + * @param {{ _id: string, roles?: string[], username?: string, name?: string, statusText?: string, email?: string, password?: string}} userData + */ +export async function validateUserEditing(userId, userData) { const editingMyself = userData._id && userId === userData._id; const canEditOtherUserInfo = await hasPermissionAsync(userId, 'edit-other-user-info'); const canEditOtherUserPassword = await hasPermissionAsync(userId, 'edit-other-user-password'); const user = await Users.findOneById(userData._id); - if (!user) { - throw new Error('error-invalid-user'); - } - - const isEditingUserRoles = (previousRoles: IUser['roles'], newRoles?: IUser['roles']) => + const isEditingUserRoles = (previousRoles, newRoles) => typeof newRoles !== 'undefined' && !_.isEqual(_.sortBy(previousRoles), _.sortBy(newRoles)); - const isEditingField = (previousValue?: string, newValue?: string) => typeof newValue !== 'undefined' && newValue !== previousValue; + const isEditingField = (previousValue, newValue) => typeof newValue !== 'undefined' && newValue !== previousValue; if (isEditingUserRoles(user.roles, userData.roles) && !(await hasPermissionAsync(userId, 'assign-roles'))) { throw new Meteor.Error('error-action-not-allowed', 'Assign roles is not allowed', { @@ -244,8 +242,8 @@ export async function validateUserEditing(userId: IUser['_id'], userData: IUpdat } } -const handleBio = (updateUser: DeepWritable>, bio?: string) => { - if (bio?.trim()) { +const handleBio = (updateUser, bio) => { + if (bio && bio.trim()) { if (bio.length > MAX_BIO_LENGTH) { throw new Meteor.Error('error-bio-size-exceeded', `Bio size exceeds ${MAX_BIO_LENGTH} characters`, { method: 'saveUserProfile', @@ -259,8 +257,8 @@ const handleBio = (updateUser: DeepWritable>, bio?: string) } }; -const handleNickname = (updateUser: DeepWritable>, nickname?: string) => { - if (nickname?.trim()) { +const handleNickname = (updateUser, nickname) => { + if (nickname && nickname.trim()) { if (nickname.length > MAX_NICKNAME_LENGTH) { throw new Meteor.Error('error-nickname-size-exceeded', `Nickname size exceeds ${MAX_NICKNAME_LENGTH} characters`, { method: 'saveUserProfile', @@ -274,7 +272,7 @@ const handleNickname = (updateUser: DeepWritable>, nickname? } }; -const saveNewUser = async function (userData: ICreateUserParams, sendPassword: boolean) { +const saveNewUser = async function (userData, sendPassword) { await validateEmailDomain(userData.email); const roles = (!!userData.roles && userData.roles.length > 0 && userData.roles) || getNewUserRoles(); @@ -285,11 +283,13 @@ const saveNewUser = async function (userData: ICreateUserParams, sendPassword: b username: userData.username, password: userData.password, joinDefaultChannels: userData.joinDefaultChannels, - ...(userData.email && { email: userData.email }), isGuest, globalRoles: roles, skipNewUserRolesSetting: true, }; + if (userData.email) { + createUser.email = userData.email; + } const _id = await Accounts.createUserAsync(createUser); @@ -321,6 +321,8 @@ const saveNewUser = async function (userData: ICreateUserParams, sendPassword: b await _sendUserEmail(settings.get('Password_Changed_Email_Subject'), passwordChangedHtml, userData); } + userData._id = _id; + if (settings.get('Accounts_SetDefaultAvatar') === true && userData.email) { const gravatarUrl = Gravatar.url(userData.email, { default: '404', @@ -329,7 +331,7 @@ const saveNewUser = async function (userData: ICreateUserParams, sendPassword: b }); try { - await setUserAvatar({ _id, username: userData.username }, gravatarUrl, '', 'url'); + await setUserAvatar(userData, gravatarUrl, '', 'url'); } catch (e) { // Ignore this error for now, as it not being successful isn't bad } @@ -340,8 +342,8 @@ const saveNewUser = async function (userData: ICreateUserParams, sendPassword: b return _id; }; -export const saveUser = async function (userId: IUser['_id'], userData: ISaveUserDataParams) { - const oldUserData = '_id' in userData && (await Users.findOneById(userData._id)); +export const saveUser = async function (userId, userData) { + const oldUserData = userData._id && (await Users.findOneById(userData._id)); if (oldUserData && isUserFederated(oldUserData)) { throw new Meteor.Error('Edit_Federated_User_Not_Allowed', 'Not possible to edit a federated user'); } @@ -365,7 +367,7 @@ export const saveUser = async function (userId: IUser['_id'], userData: ISaveUse delete userData.setRandomPassword; } - if (!('_id' in userData)) { + if (!userData._id) { return saveNewUser(userData, sendPassword); } @@ -397,7 +399,8 @@ export const saveUser = async function (userId: IUser['_id'], userData: ISaveUse } if ( - userData.password?.trim() && + userData.password && + userData.password.trim() && (await hasPermissionAsync(userId, 'edit-other-user-password')) && passwordPolicy.validate(userData.password) ) { @@ -417,6 +420,13 @@ export const saveUser = async function (userId: IUser['_id'], userData: ISaveUse if (userData.roles) { updateUser.$set.roles = userData.roles; } + if (userData.settings) { + updateUser.$set.settings = { preferences: userData.settings.preferences }; + } + + if (userData.language) { + updateUser.$set.language = userData.language; + } if (typeof userData.requirePasswordChange !== 'undefined') { updateUser.$set.requirePasswordChange = userData.requirePasswordChange; @@ -445,13 +455,8 @@ export const saveUser = async function (userId: IUser['_id'], userData: ISaveUse performedBy: await safeGetMeteorUser(), }); - if (sendPassword && userUpdated?.emails && userUpdated.emails[0].address) { - userData.email = userUpdated.emails[0].address; - await _sendUserEmail( - settings.get('Password_Changed_Email_Subject'), - passwordChangedHtml, - userData as RequiredField, - ); + if (sendPassword) { + await _sendUserEmail(settings.get('Password_Changed_Email_Subject'), passwordChangedHtml, userData); } if (typeof userData.verified === 'boolean') { diff --git a/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts b/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts index 7f606036c3c6..122b11172d57 100644 --- a/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts +++ b/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts @@ -25,6 +25,6 @@ Meteor.methods({ }); } - return saveUser(Meteor.userId(), userData as ISaveUserDataParams); + return saveUser(Meteor.userId(), userData); }), }); diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts index 63f23c2ed5bb..76d31704bc6e 100644 --- a/packages/core-typings/src/IUser.ts +++ b/packages/core-typings/src/IUser.ts @@ -138,7 +138,7 @@ export interface IUserEmail { } export interface IUserSettings { - profile?: any; + profile: any; preferences?: { [key: string]: any; }; From 8e00521c493690f73ff41bd485b0fae63257671a Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Thu, 14 Mar 2024 11:14:09 -0300 Subject: [PATCH 11/18] Add end-to-end tests --- apps/meteor/tests/end-to-end/api/users.ts | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index afb2d5fd4b37..47099965ff30 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -1675,6 +1675,44 @@ describe('[Users]', () => { .end(done); }); + it('should return an error when trying to upsert a user by sending an empty userId', (done) => { + request + .post(api('users.update')) + .set(credentials) + .send({ + userId: '', + data: {}, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + expect(res.body).to.have.property('error', 'must NOT have fewer than 1 characters [invalid-params]'); + }) + .end(done); + }); + + it('should return an error when trying to use the joinDefaultChannels param, which is not intended for updates', (done) => { + request + .post(api('users.update')) + .set(credentials) + .send({ + userId: targetUser._id, + data: { + joinDefaultChannels: true, + }, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'invalid-params'); + expect(res.body).to.have.property('error', 'must NOT have additional properties [invalid-params]'); + }) + .end(done); + }); + it("should update a bot's email", (done) => { void request .post(api('users.update')) From a45cbaa8f8f13391164658177b866a490246f229 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Thu, 9 May 2024 17:48:28 -0300 Subject: [PATCH 12/18] chore!: Improve permissions check on oauth-apps endpoints (#32338) Co-authored-by: Marcos Spessatto Defendi --- apps/meteor/app/api/server/v1/oauthapps.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index 97d489295d42..60ffbcc0f66c 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -1,7 +1,6 @@ import { OAuthApps } from '@rocket.chat/models'; import { isUpdateOAuthAppParams, isOauthAppsGetParams, isOauthAppsAddParams, isDeleteOAuthAppParams } from '@rocket.chat/rest-typings'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { addOAuthApp } from '../../../oauth2-server-config/server/admin/functions/addOAuthApp'; import { API } from '../api'; From d9fc7114284274b1b615d3de4152b9f8a8abf692 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 13 May 2024 08:49:58 -0300 Subject: [PATCH 13/18] chore!: Improve permissions check on permissions endpoints (#32343) --- apps/meteor/app/api/server/v1/oauthapps.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index 60ffbcc0f66c..97d489295d42 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -1,6 +1,7 @@ import { OAuthApps } from '@rocket.chat/models'; import { isUpdateOAuthAppParams, isOauthAppsGetParams, isOauthAppsAddParams, isDeleteOAuthAppParams } from '@rocket.chat/rest-typings'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { addOAuthApp } from '../../../oauth2-server-config/server/admin/functions/addOAuthApp'; import { API } from '../api'; From 40cca3cf96a19cfd722290ed24d8a0f9bc1fc4b0 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 14 May 2024 18:10:09 -0300 Subject: [PATCH 14/18] chore!: Improve permissions check on misc endpoints (#32337) --- .../tests/end-to-end/api/miscellaneous.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/miscellaneous.ts b/apps/meteor/tests/end-to-end/api/miscellaneous.ts index f664882cb385..fbbc6cd8b688 100644 --- a/apps/meteor/tests/end-to-end/api/miscellaneous.ts +++ b/apps/meteor/tests/end-to-end/api/miscellaneous.ts @@ -836,4 +836,43 @@ describe('miscellaneous', () => { }); }); }); + + describe('/stdout.queue', () => { + before(async () => { + return updatePermission('view-logs', ['admin']); + }); + + after(async () => { + return updatePermission('view-logs', ['admin']); + }); + + it('should return server logs', async () => { + return request + .get(api('stdout.queue')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('queue').and.to.be.an('array').that.is.not.empty; + expect(res.body.queue[0]).to.be.an('object'); + expect(res.body.queue[0]).to.have.property('id').and.to.be.a('string'); + expect(res.body.queue[0]).to.have.property('string').and.to.be.a('string'); + expect(res.body.queue[0]).to.have.property('ts').and.to.be.a('string'); + }); + }); + + it('should not return server logs if user does NOT have the view-logs permission', async () => { + await updatePermission('view-logs', []); + return request + .get(api('stdout.queue')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); }); From 256bc997f66eae3d8a81445c99b37c6c4e5beac0 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:58:38 -0300 Subject: [PATCH 15/18] Fix lint --- apps/meteor/tests/end-to-end/api/users.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index 47099965ff30..2752e1168073 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -1675,8 +1675,8 @@ describe('[Users]', () => { .end(done); }); - it('should return an error when trying to upsert a user by sending an empty userId', (done) => { - request + it('should return an error when trying to upsert a user by sending an empty userId', () => { + return request .post(api('users.update')) .set(credentials) .send({ @@ -1689,12 +1689,11 @@ describe('[Users]', () => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('errorType', 'invalid-params'); expect(res.body).to.have.property('error', 'must NOT have fewer than 1 characters [invalid-params]'); - }) - .end(done); + }); }); - it('should return an error when trying to use the joinDefaultChannels param, which is not intended for updates', (done) => { - request + it('should return an error when trying to use the joinDefaultChannels param, which is not intended for updates', () => { + return request .post(api('users.update')) .set(credentials) .send({ @@ -1709,8 +1708,7 @@ describe('[Users]', () => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('errorType', 'invalid-params'); expect(res.body).to.have.property('error', 'must NOT have additional properties [invalid-params]'); - }) - .end(done); + }); }); it("should update a bot's email", (done) => { From 7b2aab2947fbcaaad22fb4d776ea28f68f64225f Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 14 May 2024 18:10:09 -0300 Subject: [PATCH 16/18] chore!: Improve permissions check on misc endpoints (#32337) --- .../tests/end-to-end/api/miscellaneous.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/miscellaneous.ts b/apps/meteor/tests/end-to-end/api/miscellaneous.ts index fbbc6cd8b688..cd78d2ac9454 100644 --- a/apps/meteor/tests/end-to-end/api/miscellaneous.ts +++ b/apps/meteor/tests/end-to-end/api/miscellaneous.ts @@ -875,4 +875,43 @@ describe('miscellaneous', () => { }); }); }); + + describe('/stdout.queue', () => { + before(async () => { + return updatePermission('view-logs', ['admin']); + }); + + after(async () => { + return updatePermission('view-logs', ['admin']); + }); + + it('should return server logs', async () => { + return request + .get(api('stdout.queue')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('queue').and.to.be.an('array').that.is.not.empty; + expect(res.body.queue[0]).to.be.an('object'); + expect(res.body.queue[0]).to.have.property('id').and.to.be.a('string'); + expect(res.body.queue[0]).to.have.property('string').and.to.be.a('string'); + expect(res.body.queue[0]).to.have.property('ts').and.to.be.a('string'); + }); + }); + + it('should not return server logs if user does NOT have the view-logs permission', async () => { + await updatePermission('view-logs', []); + return request + .get(api('stdout.queue')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + }); + }); }); From 1e57774dc195ddcfbebecd6cfadc47310ab74ff9 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Wed, 9 Oct 2024 19:42:57 -0300 Subject: [PATCH 17/18] Remove unrelated changes --- .../tests/end-to-end/api/miscellaneous.ts | 78 ------------------- 1 file changed, 78 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/miscellaneous.ts b/apps/meteor/tests/end-to-end/api/miscellaneous.ts index cd78d2ac9454..f664882cb385 100644 --- a/apps/meteor/tests/end-to-end/api/miscellaneous.ts +++ b/apps/meteor/tests/end-to-end/api/miscellaneous.ts @@ -836,82 +836,4 @@ describe('miscellaneous', () => { }); }); }); - - describe('/stdout.queue', () => { - before(async () => { - return updatePermission('view-logs', ['admin']); - }); - - after(async () => { - return updatePermission('view-logs', ['admin']); - }); - - it('should return server logs', async () => { - return request - .get(api('stdout.queue')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('queue').and.to.be.an('array').that.is.not.empty; - expect(res.body.queue[0]).to.be.an('object'); - expect(res.body.queue[0]).to.have.property('id').and.to.be.a('string'); - expect(res.body.queue[0]).to.have.property('string').and.to.be.a('string'); - expect(res.body.queue[0]).to.have.property('ts').and.to.be.a('string'); - }); - }); - - it('should not return server logs if user does NOT have the view-logs permission', async () => { - await updatePermission('view-logs', []); - return request - .get(api('stdout.queue')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); - }); - }); - }); - - describe('/stdout.queue', () => { - before(async () => { - return updatePermission('view-logs', ['admin']); - }); - - after(async () => { - return updatePermission('view-logs', ['admin']); - }); - - it('should return server logs', async () => { - return request - .get(api('stdout.queue')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('queue').and.to.be.an('array').that.is.not.empty; - expect(res.body.queue[0]).to.be.an('object'); - expect(res.body.queue[0]).to.have.property('id').and.to.be.a('string'); - expect(res.body.queue[0]).to.have.property('string').and.to.be.a('string'); - expect(res.body.queue[0]).to.have.property('ts').and.to.be.a('string'); - }); - }); - - it('should not return server logs if user does NOT have the view-logs permission', async () => { - await updatePermission('view-logs', []); - return request - .get(api('stdout.queue')) - .set(credentials) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); - }); - }); - }); }); From 8b90e74b95a0268912a2d95b44433012fdfb2940 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:43:44 -0300 Subject: [PATCH 18/18] Update four-snakes-deny.md --- .changeset/four-snakes-deny.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/four-snakes-deny.md b/.changeset/four-snakes-deny.md index 0d5e2ca10b2a..54149bfc4fdf 100644 --- a/.changeset/four-snakes-deny.md +++ b/.changeset/four-snakes-deny.md @@ -1,6 +1,5 @@ --- "@rocket.chat/meteor": major -"@rocket.chat/core-typings": major "@rocket.chat/rest-typings": major ---