diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index 7858a236f8685..920040bbdf9b9 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -31,8 +31,13 @@ import { removePersonalAccessTokenOfUser } from '../../../../imports/personal-ac import { i18n } from '../../../../server/lib/i18n'; import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey'; import { sendWelcomeEmail } from '../../../../server/lib/sendWelcomeEmail'; +import { registerUser } from '../../../../server/methods/registerUser'; +import { requestDataDownload } from '../../../../server/methods/requestDataDownload'; +import { resetAvatar } from '../../../../server/methods/resetAvatar'; import { saveUserPreferences } from '../../../../server/methods/saveUserPreferences'; import { sendConfirmationEmail } from '../../../../server/methods/sendConfirmationEmail'; +import { sendForgotPasswordEmail } from '../../../../server/methods/sendForgotPasswordEmail'; +import { executeSetUserActiveStatus } from '../../../../server/methods/setUserActiveStatus'; import { getUserForCheck, emailCheck } from '../../../2fa/server/code'; import { resetTOTP } from '../../../2fa/server/functions/resetTOTP'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; @@ -40,7 +45,10 @@ import { checkUsernameAvailability, checkUsernameAvailabilityWithValidation, } from '../../../lib/server/functions/checkUsernameAvailability'; +import { deleteUser } from '../../../lib/server/functions/deleteUser'; +import { getAvatarSuggestionForUser } from '../../../lib/server/functions/getAvatarSuggestionForUser'; import { getFullUserDataByIdOrUsernameOrImportId } from '../../../lib/server/functions/getFullUserData'; +import { generateUsernameSuggestion } from '../../../lib/server/functions/getUsernameSuggestion'; import { saveCustomFields } from '../../../lib/server/functions/saveCustomFields'; import { saveCustomFieldsWithoutValidation } from '../../../lib/server/functions/saveCustomFieldsWithoutValidation'; import { saveUser } from '../../../lib/server/functions/saveUser'; @@ -52,6 +60,7 @@ import { validateNameChars } from '../../../lib/server/functions/validateNameCha import { validateUsername } from '../../../lib/server/functions/validateUsername'; import { notifyOnUserChange, notifyOnUserChangeAsync } from '../../../lib/server/lib/notifyListener'; import { generateAccessToken } from '../../../lib/server/methods/createToken'; +import { deleteUserOwnAccount } from '../../../lib/server/methods/deleteUserOwnAccount'; import { settings } from '../../../settings/server'; import { getURL } from '../../../utils/server/getURL'; import { API } from '../api'; @@ -87,7 +96,7 @@ API.v1.addRoute( }, { async get() { - const suggestions = await Meteor.callAsync('getAvatarSuggestion'); + const suggestions = await getAvatarSuggestionForUser(this.user); return API.v1.success({ suggestions }); }, @@ -118,7 +127,7 @@ API.v1.addRoute( confirmRelinquish, } = this.bodyParams; - await Meteor.callAsync('setUserActiveStatus', userId, active, Boolean(confirmRelinquish)); + await executeSetUserActiveStatus(this.userId, userId, active, Boolean(confirmRelinquish)); } const { fields } = await this.parseJsonQuery(); @@ -311,7 +320,7 @@ API.v1.addRoute( } if (typeof this.bodyParams.active !== 'undefined') { - await Meteor.callAsync('setUserActiveStatus', userId, this.bodyParams.active); + await executeSetUserActiveStatus(this.userId, userId, this.bodyParams.active); } const { fields } = await this.parseJsonQuery(); @@ -334,7 +343,7 @@ API.v1.addRoute( const user = await getUserFromParams(this.bodyParams); const { confirmRelinquish = false } = this.bodyParams; - await Meteor.callAsync('deleteUser', user._id, confirmRelinquish); + await deleteUser(user._id, confirmRelinquish, this.userId); return API.v1.success(); }, @@ -356,7 +365,7 @@ API.v1.addRoute( const { confirmRelinquish = false } = this.bodyParams; - await Meteor.callAsync('deleteUserOwnAccount', password, confirmRelinquish); + await deleteUserOwnAccount(this.userId, password, confirmRelinquish); return API.v1.success(); }, @@ -375,7 +384,7 @@ API.v1.addRoute( { async post() { const { userId, activeStatus, confirmRelinquish = false } = this.bodyParams; - await Meteor.callAsync('setUserActiveStatus', userId, activeStatus, confirmRelinquish); + await executeSetUserActiveStatus(this.userId, userId, activeStatus, confirmRelinquish); const user = await Users.findOneById(this.bodyParams.userId, { projection: { active: 1 } }); if (!user) { @@ -659,11 +668,15 @@ API.v1.addRoute( } // Register the user - const userId = await Meteor.callAsync('registerUser', { + const userId = await registerUser({ ...params, ...(secretURL && { secretURL }), }); + if (typeof userId !== 'string') { + return API.v1.failure('Error creating user'); + } + // Now set their username const { fields } = await this.parseJsonQuery(); await setUsernameWithValidation(userId, this.bodyParams.username); @@ -690,12 +703,12 @@ API.v1.addRoute( const user = await getUserFromParams(this.bodyParams); if (settings.get('Accounts_AllowUserAvatarChange') && user._id === this.userId) { - await Meteor.callAsync('resetAvatar'); + await resetAvatar(this.userId, this.userId); } else if ( (await hasPermissionAsync(this.userId, 'edit-other-user-avatar')) || (await hasPermissionAsync(this.userId, 'manage-moderation-actions')) ) { - await Meteor.callAsync('resetAvatar', user._id); + await resetAvatar(this.userId, user._id); } else { throw new Meteor.Error('error-not-allowed', 'Reset avatar is not allowed', { method: 'users.resetAvatar', @@ -756,7 +769,7 @@ API.v1.addRoute( return API.v1.failure("The 'email' param is required"); } - await Meteor.callAsync('sendForgotPasswordEmail', email.toLowerCase()); + await sendForgotPasswordEmail(email.toLowerCase()); return API.v1.success(); }, }, @@ -767,7 +780,7 @@ API.v1.addRoute( { authRequired: true }, { async get() { - const result = await Meteor.callAsync('getUsernameSuggestion'); + const result = await generateUsernameSuggestion(this.user); return API.v1.success({ result }); }, @@ -1035,7 +1048,7 @@ API.v1.addRoute( { async get() { const { fullExport = false } = this.queryParams; - const result = (await Meteor.callAsync('requestDataDownload', { fullExport: fullExport === 'true' })) as { + const result = (await requestDataDownload({ userData: this.user, fullExport: fullExport === 'true' })) as { requested: boolean; exportOperation: IExportOperation; }; diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index e0a217a0d39b3..fbdb3215cf00d 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { api } from '@rocket.chat/core-services'; import { isUserFederated, type IUser } from '@rocket.chat/core-typings'; import { @@ -169,6 +170,11 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele // Remove user from users database await Users.removeById(userId); + // App IPostUserDeleted event hook + if (deletedBy) { + await Apps.self?.triggerEvent(AppEvents.IPostUserDeleted, { user, performedBy: await Users.findOneById(deletedBy) }); + } + // update name and fname of group direct messages await updateGroupDMsName(user); diff --git a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts index 7b5663185bc56..478a7872ce0fe 100644 --- a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts +++ b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts @@ -17,21 +17,53 @@ declare module '@rocket.chat/ddp-client' { } } -Meteor.methods({ - async deleteUserOwnAccount(password, confirmRelinquish) { - check(password, String); +export const deleteUserOwnAccount = async (fromUserId: string, password: string, confirmRelinquish = false): Promise => { + if (!settings.get('Accounts_AllowDeleteOwnAccount')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { + method: 'deleteUserOwnAccount', + }); + } - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'deleteUserOwnAccount', - }); - } + if (!fromUserId) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'deleteUserOwnAccount', + }); + } + + const user = await Users.findOneById(fromUserId); + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'deleteUserOwnAccount', + }); + } - if (!settings.get('Accounts_AllowDeleteOwnAccount')) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { + if (user.services?.password && trim(user.services.password.bcrypt)) { + const result = await Accounts._checkPasswordAsync(user as Meteor.User, { + digest: password.toLowerCase(), + algorithm: 'sha-256', + }); + if (result.error) { + throw new Meteor.Error('error-invalid-password', 'Invalid password', { method: 'deleteUserOwnAccount', }); } + } else if (!user.username || SHA256(user.username) !== password.trim()) { + throw new Meteor.Error('error-invalid-username', 'Invalid username', { + method: 'deleteUserOwnAccount', + }); + } + + await deleteUser(fromUserId, confirmRelinquish); + + // App IPostUserDeleted event hook + await Apps.self?.triggerEvent(AppEvents.IPostUserDeleted, { user }); + + return true; +}; + +Meteor.methods({ + async deleteUserOwnAccount(password, confirmRelinquish) { + check(password, String); const uid = Meteor.userId(); if (!uid) { @@ -40,34 +72,6 @@ Meteor.methods({ }); } - const user = await Users.findOneById(uid); - if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'deleteUserOwnAccount', - }); - } - - if (user.services?.password && trim(user.services.password.bcrypt)) { - const result = await Accounts._checkPasswordAsync(user as Meteor.User, { - digest: password.toLowerCase(), - algorithm: 'sha-256', - }); - if (result.error) { - throw new Meteor.Error('error-invalid-password', 'Invalid password', { - method: 'deleteUserOwnAccount', - }); - } - } else if (!user.username || SHA256(user.username) !== password.trim()) { - throw new Meteor.Error('error-invalid-username', 'Invalid username', { - method: 'deleteUserOwnAccount', - }); - } - - await deleteUser(uid, confirmRelinquish); - - // App IPostUserDeleted event hook - await Apps.self?.triggerEvent(AppEvents.IPostUserDeleted, { user }); - - return true; + return deleteUserOwnAccount(uid, password, confirmRelinquish); }, }); diff --git a/apps/meteor/server/methods/deleteUser.ts b/apps/meteor/server/methods/deleteUser.ts index 8eaec34182889..ac542f12fa35c 100644 --- a/apps/meteor/server/methods/deleteUser.ts +++ b/apps/meteor/server/methods/deleteUser.ts @@ -1,4 +1,3 @@ -import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; @@ -15,45 +14,53 @@ declare module '@rocket.chat/ddp-client' { } } +export const executeDeleteUser = async (fromUserId: IUser['_id'], userId: IUser['_id'], confirmRelinquish = false): Promise => { + const user = await Users.findOneById(userId); + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user to delete', { + method: 'deleteUser', + }); + } + + if (user.type === 'app') { + throw new Meteor.Error('error-cannot-delete-app-user', 'Deleting app user is not allowed', { + method: 'deleteUser', + }); + } + + const adminCount = await Users.countDocuments({ roles: 'admin' }); + + const userIsAdmin = user.roles?.indexOf('admin') > -1; + + if (adminCount === 1 && userIsAdmin) { + throw new Meteor.Error('error-action-not-allowed', 'Leaving the app without admins is not allowed', { + method: 'deleteUser', + action: 'Remove_last_admin', + }); + } + + await deleteUser(userId, confirmRelinquish, fromUserId); + + return true; +}; + Meteor.methods({ async deleteUser(userId, confirmRelinquish = false) { check(userId, String); + const uid = Meteor.userId(); - if (!uid || (await hasPermissionAsync(uid, 'delete-user')) !== true) { + if (!uid) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'deleteUser', }); } - const user = await Users.findOneById(userId); - if (!user) { - throw new Meteor.Error('error-invalid-user', 'Invalid user to delete', { - method: 'deleteUser', - }); - } - - if (user.type === 'app') { - throw new Meteor.Error('error-cannot-delete-app-user', 'Deleting app user is not allowed', { - method: 'deleteUser', - }); - } - - const adminCount = await Users.countDocuments({ roles: 'admin' }); - - const userIsAdmin = user.roles?.indexOf('admin') > -1; - - if (adminCount === 1 && userIsAdmin) { - throw new Meteor.Error('error-action-not-allowed', 'Leaving the app without admins is not allowed', { + if ((await hasPermissionAsync(uid, 'delete-user')) !== true) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'deleteUser', - action: 'Remove_last_admin', }); } - await deleteUser(userId, confirmRelinquish, uid); - - // App IPostUserDeleted event hook - await Apps.self?.triggerEvent(AppEvents.IPostUserDeleted, { user, performedBy: await Meteor.userAsync() }); - - return true; + return executeDeleteUser(uid, userId, confirmRelinquish); }, }); diff --git a/apps/meteor/server/methods/registerUser.ts b/apps/meteor/server/methods/registerUser.ts index 178061aafa605..a7b6e6e4506f5 100644 --- a/apps/meteor/server/methods/registerUser.ts +++ b/apps/meteor/server/methods/registerUser.ts @@ -16,7 +16,7 @@ declare module '@rocket.chat/ddp-client' { interface ServerMethods { registerUser( formData: - | { email: string; pass: string; username: IUser['username']; name: string; secretURL?: string; reason?: string } + | { email: string; pass: string; username: IUser['username']; name?: string; secretURL?: string; reason?: string } | { email?: null }, ): | { @@ -27,101 +27,115 @@ declare module '@rocket.chat/ddp-client' { } } -Meteor.methods({ - async registerUser(formData) { - const AllowAnonymousRead = settings.get('Accounts_AllowAnonymousRead'); - const AllowAnonymousWrite = settings.get('Accounts_AllowAnonymousWrite'); - const manuallyApproveNewUsers = settings.get('Accounts_ManuallyApproveNewUsers'); - if (AllowAnonymousRead === true && AllowAnonymousWrite === true && !formData.email) { - const userId = await Accounts.insertUserDoc( - {}, - { - globalRoles: ['anonymous'], - active: true, - }, - ); - - const stampedLoginToken = await Accounts._generateStampedLoginToken(); - - await Accounts._insertLoginToken(userId, stampedLoginToken); - return stampedLoginToken; - } - check( - formData, - Match.ObjectIncluding({ - email: String, - pass: String, - name: String, - secretURL: Match.Optional(String), - reason: Match.Optional(String), - }), +export const registerUser = async ( + formData: + | { email: string; pass: string; username: IUser['username']; name?: string; secretURL?: string; reason?: string } + | { email?: null }, +): Promise< + | { + token: string; + when: Date; + } + | string +> => { + const AllowAnonymousRead = settings.get('Accounts_AllowAnonymousRead'); + const AllowAnonymousWrite = settings.get('Accounts_AllowAnonymousWrite'); + const manuallyApproveNewUsers = settings.get('Accounts_ManuallyApproveNewUsers'); + if (AllowAnonymousRead === true && AllowAnonymousWrite === true && !formData.email) { + const userId = await Accounts.insertUserDoc( + {}, + { + globalRoles: ['anonymous'], + active: true, + }, ); - if (settings.get('Accounts_RegistrationForm') === 'Disabled') { - throw new Meteor.Error('error-user-registration-disabled', 'User registration is disabled', { + const stampedLoginToken = await Accounts._generateStampedLoginToken(); + + await Accounts._insertLoginToken(userId, stampedLoginToken); + return stampedLoginToken; + } + check( + formData, + Match.ObjectIncluding({ + email: String, + pass: String, + name: String, + secretURL: Match.Optional(String), + reason: Match.Optional(String), + }), + ); + + if (settings.get('Accounts_RegistrationForm') === 'Disabled') { + throw new Meteor.Error('error-user-registration-disabled', 'User registration is disabled', { + method: 'registerUser', + }); + } + + if ( + settings.get('Accounts_RegistrationForm') === 'Secret URL' && + (!formData.secretURL || formData.secretURL !== settings.get('Accounts_RegistrationForm_SecretURL')) + ) { + if (!formData.secretURL) { + throw new Meteor.Error('error-user-registration-secret', 'User registration is only allowed via Secret URL', { method: 'registerUser', }); } - if ( - settings.get('Accounts_RegistrationForm') === 'Secret URL' && - (!formData.secretURL || formData.secretURL !== settings.get('Accounts_RegistrationForm_SecretURL')) - ) { - if (!formData.secretURL) { - throw new Meteor.Error('error-user-registration-secret', 'User registration is only allowed via Secret URL', { - method: 'registerUser', - }); - } - - try { - await validateInviteToken(formData.secretURL); - } catch (e) { - throw new Meteor.Error('error-user-registration-secret', 'User registration is only allowed via Secret URL', { - method: 'registerUser', - }); - } + try { + await validateInviteToken(formData.secretURL); + } catch (e) { + throw new Meteor.Error('error-user-registration-secret', 'User registration is only allowed via Secret URL', { + method: 'registerUser', + }); } + } - passwordPolicy.validate(formData.pass); - - await validateEmailDomain(formData.email); + passwordPolicy.validate(formData.pass); - const userData = { - email: trim(formData.email.toLowerCase()), - password: formData.pass, - name: formData.name, - reason: formData.reason, - }; + await validateEmailDomain(formData.email); - let userId; - try { - userId = await Accounts.createUserAsync(userData); - } catch (e) { - if (e instanceof Meteor.Error) { - throw e; - } + const userData = { + email: trim(formData.email.toLowerCase()), + password: formData.pass, + name: formData.name, + reason: formData.reason, + }; - if (e instanceof Error) { - throw new Meteor.Error(e.message); - } + let userId; + try { + userId = await Accounts.createUserAsync(userData); + } catch (e) { + if (e instanceof Meteor.Error) { + throw e; + } - throw new Meteor.Error(String(e)); + if (e instanceof Error) { + throw new Meteor.Error(e.message); } - await Users.setName(userId, trim(formData.name)); + throw new Meteor.Error(String(e)); + } - const reason = trim(formData.reason); - if (manuallyApproveNewUsers && reason) { - await Users.setReason(userId, reason); - } + await Users.setName(userId, trim(formData.name)); - try { - Accounts.sendVerificationEmail(userId, userData.email); - } catch (error) { - // throw new Meteor.Error 'error-email-send-failed', 'Error trying to send email: ' + error.message, { method: 'registerUser', message: error.message } - } + const reason = trim(formData.reason); + if (manuallyApproveNewUsers && reason) { + await Users.setReason(userId, reason); + } + + try { + Accounts.sendVerificationEmail(userId, userData.email); + } catch (error) { + // throw new Meteor.Error 'error-email-send-failed', 'Error trying to send email: ' + error.message, { method: 'registerUser', message: error.message } + } - return userId; + return userId; +}; + +Meteor.methods({ + async registerUser(formData) { + return registerUser(formData); }, }); diff --git a/apps/meteor/server/methods/requestDataDownload.ts b/apps/meteor/server/methods/requestDataDownload.ts index cf30a6768b7cc..6c489b3bf039d 100644 --- a/apps/meteor/server/methods/requestDataDownload.ts +++ b/apps/meteor/server/methods/requestDataDownload.ts @@ -2,7 +2,7 @@ import { mkdtemp } from 'fs/promises'; import { tmpdir } from 'os'; import path, { join } from 'path'; -import type { IExportOperation } from '@rocket.chat/core-typings'; +import type { IExportOperation, IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { ExportOperations, UserDataFiles } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -22,78 +22,99 @@ declare module '@rocket.chat/ddp-client' { } } -Meteor.methods({ - async requestDataDownload({ fullExport = false }) { - const currentUserData = await Meteor.userAsync(); - - if (!currentUserData) { - throw new Meteor.Error('error-invalid-user', 'Invalid user'); - } +export const requestDataDownload = async ({ + userData, + fullExport = false, +}: { + userData: IUser; + fullExport?: boolean; +}): Promise<{ + requested: boolean; + exportOperation: IExportOperation; + url: string | null; + pendingOperationsBeforeMyRequest: number; +}> => { + const currentUserData = userData; + + if (!currentUserData) { + throw new Meteor.Error('error-invalid-user', 'Invalid user'); + } - const userId = currentUserData._id; - - const lastOperation = await ExportOperations.findLastOperationByUser(userId, fullExport); - const requestDay = lastOperation ? lastOperation.createdAt : new Date(); - const pendingOperationsBeforeMyRequestCount = await ExportOperations.countAllPendingBeforeMyRequest(requestDay); - - if (lastOperation) { - const yesterday = new Date(); - yesterday.setUTCDate(yesterday.getUTCDate() - 1); - - if (lastOperation.createdAt > yesterday) { - if (lastOperation.status === 'completed') { - const file = lastOperation.fileId - ? await UserDataFiles.findOneById(lastOperation.fileId) - : await UserDataFiles.findLastFileByUser(userId); - if (file) { - return { - requested: false, - exportOperation: lastOperation, - url: dataExport.getPath(file._id), - pendingOperationsBeforeMyRequest: pendingOperationsBeforeMyRequestCount, - }; - } + const userId = currentUserData._id; + + const lastOperation = await ExportOperations.findLastOperationByUser(userId, fullExport); + const requestDay = lastOperation ? lastOperation.createdAt : new Date(); + const pendingOperationsBeforeMyRequestCount = await ExportOperations.countAllPendingBeforeMyRequest(requestDay); + + if (lastOperation) { + const yesterday = new Date(); + yesterday.setUTCDate(yesterday.getUTCDate() - 1); + + if (lastOperation.createdAt > yesterday) { + if (lastOperation.status === 'completed') { + const file = lastOperation.fileId + ? await UserDataFiles.findOneById(lastOperation.fileId) + : await UserDataFiles.findLastFileByUser(userId); + if (file) { + return { + requested: false, + exportOperation: lastOperation, + url: dataExport.getPath(file._id), + pendingOperationsBeforeMyRequest: pendingOperationsBeforeMyRequestCount, + }; } - - return { - requested: false, - exportOperation: lastOperation, - url: null, - pendingOperationsBeforeMyRequest: pendingOperationsBeforeMyRequestCount, - }; } + + return { + requested: false, + exportOperation: lastOperation, + url: null, + pendingOperationsBeforeMyRequest: pendingOperationsBeforeMyRequestCount, + }; } + } + + const tempFolder = settings.get('UserData_FileSystemPath')?.trim() || (await mkdtemp(join(tmpdir(), 'userData'))); + + const exportOperation = { + status: 'preparing', + userId: currentUserData._id, + roomList: undefined, + fileList: [], + generatedFile: undefined, + fullExport, + userData: currentUserData, + } as unknown as IExportOperation; // @todo yikes! - const tempFolder = settings.get('UserData_FileSystemPath')?.trim() || (await mkdtemp(join(tmpdir(), 'userData'))); + const id = await ExportOperations.create(exportOperation); + exportOperation._id = id; - const exportOperation = { - status: 'preparing', - userId: currentUserData._id, - roomList: undefined, - fileList: [], - generatedFile: undefined, - fullExport, - userData: currentUserData, - } as unknown as IExportOperation; // @todo yikes! + const folderName = path.join(tempFolder, id); - const id = await ExportOperations.create(exportOperation); - exportOperation._id = id; + const assetsFolder = path.join(folderName, 'assets'); - const folderName = path.join(tempFolder, id); + exportOperation.exportPath = folderName; + exportOperation.assetsPath = assetsFolder; + exportOperation.status = 'pending'; - const assetsFolder = path.join(folderName, 'assets'); + await ExportOperations.updateOperation(exportOperation); - exportOperation.exportPath = folderName; - exportOperation.assetsPath = assetsFolder; - exportOperation.status = 'pending'; + return { + requested: true, + exportOperation, + url: null, + pendingOperationsBeforeMyRequest: pendingOperationsBeforeMyRequestCount, + }; +}; + +Meteor.methods({ + async requestDataDownload({ fullExport = false }) { + const currentUserData = await Meteor.userAsync(); - await ExportOperations.updateOperation(exportOperation); + if (!currentUserData) { + throw new Meteor.Error('error-invalid-user', 'Invalid user'); + } - return { - requested: true, - exportOperation, - url: null, - pendingOperationsBeforeMyRequest: pendingOperationsBeforeMyRequestCount, - }; + return requestDataDownload({ userData: currentUserData as IUser, fullExport }); }, }); diff --git a/apps/meteor/server/methods/resetAvatar.ts b/apps/meteor/server/methods/resetAvatar.ts index 8df7beb725453..d6d2429658367 100644 --- a/apps/meteor/server/methods/resetAvatar.ts +++ b/apps/meteor/server/methods/resetAvatar.ts @@ -16,45 +16,50 @@ declare module '@rocket.chat/ddp-client' { } } -Meteor.methods({ - async resetAvatar(userId) { - const uid = Meteor.userId(); - if (!uid) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'resetAvatar', - }); - } - const canEditOtherUserAvatar = await hasPermissionAsync(uid, 'edit-other-user-avatar'); +export const resetAvatar = async (fromUserId: IUser['_id'], userId: IUser['_id']): Promise => { + const canEditOtherUserAvatar = await hasPermissionAsync(fromUserId, 'edit-other-user-avatar'); + + if (!settings.get('Accounts_AllowUserAvatarChange') && !canEditOtherUserAvatar) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { + method: 'resetAvatar', + }); + } - if (!settings.get('Accounts_AllowUserAvatarChange') && !canEditOtherUserAvatar) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { + let user; + + if (userId !== fromUserId) { + if (!canEditOtherUserAvatar) { + throw new Meteor.Error('error-unauthorized', 'Unauthorized', { method: 'resetAvatar', }); } - let user; + user = await Users.findOneById(userId, { projection: { _id: 1, username: 1 } }); + } else { + user = await Users.findOneById(fromUserId, { projection: { _id: 1, username: 1 } }); + } - if (userId && userId !== uid) { - if (!canEditOtherUserAvatar) { - throw new Meteor.Error('error-unauthorized', 'Unauthorized', { - method: 'resetAvatar', - }); - } + if (!user?.username) { + throw new Meteor.Error('error-invalid-desired-user', 'Invalid desired user', { + method: 'resetAvatar', + }); + } - user = await Users.findOneById(userId, { projection: { _id: 1, username: 1 } }); - } else { - user = await Meteor.userAsync(); - } + await FileUpload.getStore('Avatars').deleteByName(user.username); + await Users.unsetAvatarData(user._id); + void api.broadcast('user.avatarUpdate', { username: user.username, avatarETag: undefined }); +}; - if (!user?.username) { - throw new Meteor.Error('error-invalid-desired-user', 'Invalid desired user', { +Meteor.methods({ + async resetAvatar(userId) { + const uid = Meteor.userId(); + if (!uid) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'resetAvatar', }); } - await FileUpload.getStore('Avatars').deleteByName(user.username); - await Users.unsetAvatarData(user._id); - void api.broadcast('user.avatarUpdate', { username: user.username, avatarETag: undefined }); + return resetAvatar(uid, userId); }, }); diff --git a/apps/meteor/server/methods/sendForgotPasswordEmail.ts b/apps/meteor/server/methods/sendForgotPasswordEmail.ts index 7b8082812dde6..6d7706d25c7ba 100644 --- a/apps/meteor/server/methods/sendForgotPasswordEmail.ts +++ b/apps/meteor/server/methods/sendForgotPasswordEmail.ts @@ -14,29 +14,33 @@ declare module '@rocket.chat/ddp-client' { } } -Meteor.methods({ - async sendForgotPasswordEmail(to) { - check(to, String); +export const sendForgotPasswordEmail = async (to: string): Promise => { + const email = to.trim().toLowerCase(); - const email = to.trim().toLowerCase(); + const user = await Users.findOneByEmailAddress(email, { projection: { _id: 1, services: 1 } }); - const user = await Users.findOneByEmailAddress(email, { projection: { _id: 1, services: 1 } }); + if (!user) { + return true; + } - if (!user) { - return true; + if (user.services && !user.services.password) { + if (!settings.get('Accounts_AllowPasswordChangeForOAuthUsers')) { + return false; } + } - if (user.services && !user.services.password) { - if (!settings.get('Accounts_AllowPasswordChangeForOAuthUsers')) { - return false; - } - } + try { + Accounts.sendResetPasswordEmail(user._id, email); + return true; + } catch (error) { + SystemLogger.error(error); + } +}; - try { - Accounts.sendResetPasswordEmail(user._id, email); - return true; - } catch (error) { - SystemLogger.error(error); - } +Meteor.methods({ + async sendForgotPasswordEmail(to) { + check(to, String); + + return sendForgotPasswordEmail(to); }, }); diff --git a/apps/meteor/server/methods/setUserActiveStatus.ts b/apps/meteor/server/methods/setUserActiveStatus.ts index a14616c30c047..58222fbd7be72 100644 --- a/apps/meteor/server/methods/setUserActiveStatus.ts +++ b/apps/meteor/server/methods/setUserActiveStatus.ts @@ -12,27 +12,35 @@ declare module '@rocket.chat/ddp-client' { } } -Meteor.methods({ - async setUserActiveStatus(userId, active, confirmRelinquish) { - check(userId, String); - check(active, Boolean); +export const executeSetUserActiveStatus = async ( + fromUserId: string, + userId: string, + active: boolean, + confirmRelinquish?: boolean, +): Promise => { + check(userId, String); + check(active, Boolean); + + if (!fromUserId || (await hasPermissionAsync(fromUserId, 'edit-other-user-active-status')) !== true) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { + method: 'setUserActiveStatus', + }); + } - if (!Meteor.userId()) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - method: 'setUserActiveStatus', - }); - } + await setUserActiveStatus(userId, active, confirmRelinquish); - const uid = Meteor.userId(); + return true; +}; - if (!uid || (await hasPermissionAsync(uid, 'edit-other-user-active-status')) !== true) { - throw new Meteor.Error('error-not-allowed', 'Not allowed', { +Meteor.methods({ + async setUserActiveStatus(userId, active, confirmRelinquish) { + const uid = Meteor.userId(); + if (!uid) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setUserActiveStatus', }); } - await setUserActiveStatus(userId, active, confirmRelinquish); - - return true; + return executeSetUserActiveStatus(uid, userId, active, confirmRelinquish); }, });