import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Accounts } from 'meteor/accounts-base'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Users } from '@rocket.chat/models'; import type { UserStatus } from '@rocket.chat/core-typings'; import { saveCustomFields, passwordPolicy } from '../../app/lib/server'; import { validateUserEditing } from '../../app/lib/server/functions/saveUser'; import { settings as rcSettings } from '../../app/settings/server'; import { twoFactorRequired } from '../../app/2fa/server/twoFactorRequired'; import { saveUserIdentity } from '../../app/lib/server/functions/saveUserIdentity'; import { compareUserPassword } from '../lib/compareUserPassword'; import { compareUserPasswordHistory } from '../lib/compareUserPasswordHistory'; import { AppEvents, Apps } from '../../ee/server/apps/orchestrator'; import { setUserStatusMethod } from '../../app/user-status/server/methods/setUserStatus'; async function saveUserProfile( this: Meteor.MethodThisType, settings: { email?: string; username?: string; realname?: string; newPassword?: string; statusText?: string; statusType?: string; bio?: string; nickname?: string; }, customFields: Record, ..._: unknown[] ) { if (!rcSettings.get('Accounts_AllowUserProfileChange')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'saveUserProfile', }); } if (!this.userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'saveUserProfile', }); } await validateUserEditing(this.userId, { _id: this.userId, email: settings.email, username: settings.username, name: settings.realname, password: settings.newPassword, statusText: settings.statusText, }); const user = await Users.findOneById(this.userId); if (settings.realname || settings.username) { if ( !(await saveUserIdentity({ _id: this.userId, name: settings.realname, username: settings.username, })) ) { throw new Meteor.Error('error-could-not-save-identity', 'Could not save user identity', { method: 'saveUserProfile', }); } } if (settings.statusText || settings.statusText === '') { await setUserStatusMethod(this.userId, undefined, settings.statusText); } if (settings.statusType) { await setUserStatusMethod(this.userId, settings.statusType as UserStatus, undefined); } if (user && settings.bio) { if (typeof settings.bio !== 'string' || settings.bio.length > 260) { throw new Meteor.Error('error-invalid-field', 'bio', { method: 'saveUserProfile', }); } await Users.setBio(user._id, settings.bio.trim()); } if (user && settings.nickname) { if (typeof settings.nickname !== 'string' || settings.nickname.length > 120) { throw new Meteor.Error('error-invalid-field', 'nickname', { method: 'saveUserProfile', }); } await Users.setNickname(user._id, settings.nickname.trim()); } if (settings.email) { await Meteor.callAsync('setEmail', settings.email); } const canChangePasswordForOAuth = rcSettings.get('Accounts_AllowPasswordChangeForOAuthUsers'); if (canChangePasswordForOAuth || user?.services?.password) { // Should be the last check to prevent error when trying to check password for users without password if (settings.newPassword && rcSettings.get('Accounts_AllowPasswordChange') === true && user?.services?.password?.bcrypt) { // don't let user change to same password if (user && (await compareUserPassword(user, { plain: settings.newPassword }))) { throw new Meteor.Error('error-password-same-as-current', 'Entered password same as current password', { method: 'saveUserProfile', }); } if (user?.services?.passwordHistory && !(await compareUserPasswordHistory(user, { plain: settings.newPassword }))) { throw new Meteor.Error('error-password-in-history', 'Entered password has been previously used', { method: 'saveUserProfile', }); } passwordPolicy.validate(settings.newPassword); await Accounts.setPasswordAsync(this.userId, settings.newPassword, { logout: false, }); await Users.addPasswordToHistory( this.userId, user.services?.password.bcrypt, rcSettings.get('Accounts_Password_History_Amount'), ); try { await Meteor.callAsync('removeOtherTokens'); } catch (e) { Accounts._clearAllLoginTokens(this.userId); } } } await Users.setProfile(this.userId, {}); if (customFields && Object.keys(customFields).length) { await saveCustomFields(this.userId, customFields); } // App IPostUserUpdated event hook const updatedUser = await Users.findOneById(this.userId); await Apps.triggerEvent(AppEvents.IPostUserUpdated, { user: updatedUser, previousUser: user }); return true; } const saveUserProfileWithTwoFactor = twoFactorRequired(saveUserProfile, { requireSecondFactor: true, }); declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { saveUserProfile( settings: { email?: string; username?: string; realname?: string; newPassword?: string; statusText?: string; statusType?: string; bio?: string; nickname?: string; }, customFields: Record, ...args: unknown[] ): boolean; } } Meteor.methods({ async saveUserProfile(settings, customFields, ...args) { check(settings, Object); check(customFields, Match.Maybe(Object)); if (settings.email || settings.newPassword) { return saveUserProfileWithTwoFactor.call(this, settings, customFields, ...args); } return saveUserProfile.call(this, settings, customFields, ...args); }, });