Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
413b0cd
refactor(api): update messageSearch method to retrieve user object di…
ggazzo Dec 30, 2025
d305d52
refactor(api): update followMessage and unfollowMessage methods to ac…
ggazzo Dec 30, 2025
dc23481
refactor(api): update pinMessage and unpinMessage methods to use user…
ggazzo Dec 30, 2025
c0949df
refactor(api): update eraseRoom function to accept user object instea…
ggazzo Dec 30, 2025
da07a92
refactor(apps): use route handler user instead of Meteor.userAsync()
d-gubert Dec 30, 2025
3d731e3
refactor(api): remove unused Meteor import from rest.ts for cleaner code
ggazzo Dec 30, 2025
b752290
refactor(api): update setEmail, setStatusText, and setUserStatus meth…
ggazzo Dec 31, 2025
214691c
refactor(api): update getUserFromParams and related methods to accept…
ggazzo Dec 31, 2025
28efef0
refactor(api): add user validation in saveUserProfile method to ensur…
ggazzo Dec 31, 2025
98c6383
refactor(api): enhance twoFactorRequired and saveUserProfile methods …
ggazzo Jan 2, 2026
54fa492
refactor(api): update twoFactorRequired and related methods to improv…
ggazzo Jan 2, 2026
255045e
refactor(api): simplify role creation logic by removing enterprise fe…
ggazzo Jan 26, 2026
d531c0e
fix(api): update eraseTeam test to assert user context instead of har…
ggazzo Jan 26, 2026
7b851e1
refactor(api): update user type definitions in status handling method…
ggazzo Jan 27, 2026
01ea749
refactor(api): update saveUser function to use oldUserData for status…
ggazzo Jan 27, 2026
11acd88
refactor(api): enhance type safety in eraseTeam and eraseRoom functio…
ggazzo Jan 27, 2026
4d6bf17
review 1
ggazzo Jan 28, 2026
79b20d2
refactor(api): replace type in twoFactorRequired and saveUserProfile…
ggazzo Jan 28, 2026
7bda6f6
review 2
ggazzo Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions apps/meteor/app/2fa/server/twoFactorRequired.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@ import { Meteor } from 'meteor/meteor';
import type { ITwoFactorOptions } from './code/index';
import { checkCodeForUser } from './code/index';

export function twoFactorRequired<TFunction extends (this: Meteor.MethodThisType, ...args: any[]) => any>(
fn: TFunction,
export type AuthenticatedContext = {
userId: string;
token: string;
connection: {
id: string;
clientAddress: string;
httpHeaders: Record<string, string>;
};
twoFactorChecked: boolean;
};

export const twoFactorRequired = <TFunction extends (this: any, ...args: any) => Promise<any>>(
fn: ThisParameterType<TFunction> extends AuthenticatedContext
? TFunction
: (this: AuthenticatedContext, ...args: Parameters<TFunction>) => ReturnType<TFunction>,
options?: ITwoFactorOptions,
): (this: Meteor.MethodThisType, ...args: Parameters<TFunction>) => Promise<ReturnType<TFunction>> {
return async function (this: Meteor.MethodThisType, ...args: Parameters<TFunction>): Promise<ReturnType<TFunction>> {
) =>
async function (this, ...args) {
if (!this.userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'twoFactorRequired' });
}
Expand Down Expand Up @@ -35,5 +48,4 @@ export function twoFactorRequired<TFunction extends (this: Meteor.MethodThisType
}

return fn.apply(this, args);
};
}
} as (this: ThisParameterType<TFunction>, ...args: Parameters<TFunction>) => ReturnType<TFunction>;
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/lib/eraseTeam.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ describe('eraseTeam (TypeScript) module', () => {

await subject.eraseTeam(user, team, []);

sinon.assert.calledWith(eraseRoomStub, team.roomId, 'u1');
sinon.assert.calledWith(eraseRoomStub, team.roomId, user);
});
});

Expand Down
16 changes: 8 additions & 8 deletions apps/meteor/app/api/server/lib/eraseTeam.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { AppEvents, Apps } from '@rocket.chat/apps';
import { MeteorError, Team } from '@rocket.chat/core-services';
import type { AtLeast, IRoom, ITeam, IUser } from '@rocket.chat/core-typings';
import type { IRoom, ITeam, IUser, AtLeast } from '@rocket.chat/core-typings';
import { Rooms } from '@rocket.chat/models';

import { eraseRoom } from '../../../../server/lib/eraseRoom';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { deleteRoom } from '../../../lib/server/functions/deleteRoom';

type eraseRoomFnType = (rid: string, user: AtLeast<IUser, '_id' | 'username' | 'name'>) => Promise<boolean | void>;
type EraseRoomFnType = <T extends AtLeast<IUser, '_id' | 'name' | 'username'>>(rid: string, user: T) => Promise<boolean | void>;

export const eraseTeamShared = async (
user: AtLeast<IUser, '_id' | 'username' | 'name'>,
export const eraseTeamShared = async <T extends AtLeast<IUser, '_id' | 'name' | 'username'>>(
user: T,
team: ITeam,
roomsToRemove: IRoom['_id'][] = [],
eraseRoomFn: eraseRoomFnType,
eraseRoomFn: EraseRoomFnType,
) => {
const rooms: string[] = roomsToRemove.length
? (await Team.getMatchingTeamRooms(team._id, roomsToRemove)).filter((roomId) => roomId !== team.roomId)
Expand Down Expand Up @@ -41,9 +41,9 @@ export const eraseTeamShared = async (
await Team.deleteById(team._id);
};

export const eraseTeam = async (user: AtLeast<IUser, '_id' | 'username' | 'name'>, team: ITeam, roomsToRemove: IRoom['_id'][]) => {
export const eraseTeam = async (user: IUser, team: ITeam, roomsToRemove: IRoom['_id'][]) => {
await eraseTeamShared(user, team, roomsToRemove, async (rid, user) => {
return eraseRoom(rid, user._id);
return eraseRoom(rid, user);
});
};

Expand All @@ -54,7 +54,7 @@ export const eraseTeam = async (user: AtLeast<IUser, '_id' | 'username' | 'name'
*/
export const eraseTeamOnRelinquishRoomOwnerships = async (team: ITeam, roomsToRemove: IRoom['_id'][] = []): Promise<string[]> => {
const deletedRooms = new Set<string>();
await eraseTeamShared({ _id: 'rocket.cat', username: 'rocket.cat', name: 'Rocket.Cat' }, team, roomsToRemove, async (rid) => {
await eraseTeamShared({ _id: 'rocket.cat', username: 'rocket.cat', name: 'Rocket.Cat' } as IUser, team, roomsToRemove, async (rid) => {
const isDeleted = await eraseRoomLooseValidation(rid);
if (isDeleted) {
deletedRooms.add(rid);
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/v1/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ API.v1.addRoute(
checkedArchived: false,
});

await eraseRoom(room._id, this.userId);
await eraseRoom(room._id, this.user);

return API.v1.success();
},
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/api/server/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ API.v1.addRoute(
throw new Meteor.Error('The required "mid" body param is missing.');
}

await followMessage(this.userId, { mid });
await followMessage(this.user, { mid });

return API.v1.success();
},
Expand All @@ -822,7 +822,7 @@ API.v1.addRoute(
throw new Meteor.Error('The required "mid" body param is missing.');
}

await unfollowMessage(this.userId, { mid });
await unfollowMessage(this.user, { mid });

return API.v1.success();
},
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/v1/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ API.v1.addRoute(
checkedArchived: false,
});

await eraseRoom(findResult.rid, this.userId);
await eraseRoom(findResult.rid, this.user);

return API.v1.success();
},
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/v1/im.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const dmDeleteAction = <Path extends string>(_path: Path): TypedAction<typeof dm
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}

await eraseRoom(room._id, this.userId);
await eraseRoom(room._id, this.user);

return API.v1.success();
};
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/v1/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ API.v1.addRoute(
});
}

await eraseRoom(room, this.userId);
await eraseRoom(room, this.user);

return API.v1.success();
},
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/v1/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ API.v1.addRoute(

if (rooms.length) {
for await (const room of rooms) {
await eraseRoom(room, this.userId);
await eraseRoom(room, this.user);
}
}

Expand Down
17 changes: 4 additions & 13 deletions apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import { regeneratePersonalAccessTokenOfUser } from '../../../../imports/persona
import { removePersonalAccessTokenOfUser } from '../../../../imports/personal-access-tokens/server/api/methods/removeToken';
import { UserChangedAuditStore } from '../../../../server/lib/auditServerEvents/userChanged';
import { i18n } from '../../../../server/lib/i18n';
import { removeOtherTokens } from '../../../../server/lib/removeOtherTokens';
import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey';
import { registerUser } from '../../../../server/methods/registerUser';
import { requestDataDownload } from '../../../../server/methods/requestDataDownload';
Expand Down Expand Up @@ -187,13 +186,7 @@ API.v1.addRoute(
twoFactorMethod: 'password',
};

await executeSaveUserProfile.call(
this as unknown as Meteor.MethodThisType,
this.user,
userData,
this.bodyParams.customFields,
twoFactorOptions,
);
await executeSaveUserProfile.call(this, this.user, userData, this.bodyParams.customFields, twoFactorOptions);

return API.v1.success({
user: await Users.findOneById(this.userId, { projection: API.v1.defaultFieldsToExclude }),
Expand Down Expand Up @@ -1239,7 +1232,7 @@ API.v1.addRoute(
{ authRequired: true },
{
async post() {
return API.v1.success(await removeOtherTokens(this.userId, this.connection.id));
return API.v1.success(await Users.removeNonLoginTokensExcept(this.userId, this.token));
},
},
);
Expand Down Expand Up @@ -1410,9 +1403,7 @@ API.v1.addRoute(
});
}

const user = await (async (): Promise<
Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'statusText' | 'roles'> | undefined | null
> => {
const user = await (async () => {
if (isUserFromParams(this.bodyParams, this.userId, this.user)) {
return Users.findOneById(this.userId);
}
Expand All @@ -1429,7 +1420,7 @@ API.v1.addRoute(
let { statusText, status } = user;

if (this.bodyParams.message || this.bodyParams.message === '') {
await setStatusText(user._id, this.bodyParams.message, { emit: false });
await setStatusText(user, this.bodyParams.message, { emit: false });
statusText = this.bodyParams.message;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/lib/server/functions/saveUser/saveUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const _saveUser = (session?: ClientSession) =>
}

if (typeof userData.statusText === 'string') {
await setStatusText(userData._id, userData.statusText, { updater, session });
await setStatusText(oldUserData, userData.statusText, { updater, session });
}

if (userData.email) {
Expand Down
15 changes: 1 addition & 14 deletions apps/meteor/app/lib/server/functions/setStatusText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ClientSession } from 'mongodb';
import { onceTransactionCommitedSuccessfully } from '../../../../server/database/utils';

export async function setStatusText(
userId: string,
user: Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'roles' | 'statusText'>,
statusText: string,
{
updater,
Expand All @@ -19,21 +19,8 @@ export async function setStatusText(
emit?: boolean;
} = {},
): Promise<boolean> {
if (!userId) {
return false;
}

statusText = statusText.trim().substr(0, 120);

const user = await Users.findOneById<Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'roles' | 'statusText'>>(userId, {
projection: { username: 1, name: 1, status: 1, roles: 1, statusText: 1 },
session,
});

if (!user) {
return false;
}

if (user.statusText === statusText) {
return true;
}
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/app/lib/server/methods/saveSetting.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SettingValue } from '@rocket.chat/core-typings';
import type { SettingEditor, SettingValue } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Settings } from '@rocket.chat/models';
import { Match, check } from 'meteor/check';
Expand All @@ -14,12 +14,12 @@ import { notifyOnSettingChanged } from '../lib/notifyListener';
declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
saveSetting(_id: string, value: SettingValue, editor?: string): Promise<boolean>;
saveSetting(_id: string, value: SettingValue, editor: SettingEditor): Promise<boolean>;
}
}

Meteor.methods<ServerMethods>({
saveSetting: twoFactorRequired(async function (_id, value, editor) {
saveSetting: twoFactorRequired(async function (_id: string, value: SettingValue, editor: SettingEditor) {
const uid = Meteor.userId();
if (!uid) {
throw new Meteor.Error('error-action-not-allowed', 'Editing settings is not allowed', {
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/message-pin/server/pinMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export async function pinMessage(message: IMessage, userId: string, pinnedAt?: D
}

// App IPostMessagePinned event hook
await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned);
await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, me, originalMessage.pinned);

const pinMessageType = originalMessage.t === 'e2e' ? 'message_pinned_e2e' : 'message_pinned';

Expand Down Expand Up @@ -189,7 +189,7 @@ export const unpinMessage = async (userId: string, message: IMessage) => {
}

// App IPostMessagePinned event hook
await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned);
await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, me, originalMessage.pinned);

await Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned);
if (settings.get('Message_Read_Receipt_Store_Users')) {
Expand Down
14 changes: 11 additions & 3 deletions apps/meteor/app/slashcommands-status/server/status.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import type { SlashCommandCallbackParams, IUser } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';

import { i18n } from '../../../server/lib/i18n';
Expand All @@ -14,11 +14,19 @@ slashCommands.add({
return;
}

const user = await Users.findOneById(userId, { projection: { language: 1 } });
const user = await Users.findOneById<Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'roles' | 'statusText' | 'language'>>(
userId,
{
projection: { language: 1, username: 1, name: 1, status: 1, roles: 1, statusText: 1 },
},
);
const lng = user?.language || settings.get('Language') || 'en';

if (!user) {
return;
}
try {
await setUserStatusMethod(userId, undefined, params);
await setUserStatusMethod(user, undefined, params);

void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('StatusMessage_Changed_Successfully', { lng }),
Expand Down
16 changes: 8 additions & 8 deletions apps/meteor/app/threads/server/methods/followMessage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Apps, AppEvents } from '@rocket.chat/apps';
import type { IMessage } from '@rocket.chat/core-typings';
import type { IMessage, IUser } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Messages } from '@rocket.chat/models';
import { check } from 'meteor/check';
Expand All @@ -18,7 +18,7 @@ declare module '@rocket.chat/ddp-client' {
}
}

export const followMessage = async (userId: string, { mid }: { mid: IMessage['_id'] }): Promise<false | undefined> => {
export const followMessage = async (user: IUser, { mid }: { mid: IMessage['_id'] }): Promise<false | undefined> => {
if (mid && !settings.get('Threads_enabled')) {
throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'followMessage' });
}
Expand All @@ -30,20 +30,20 @@ export const followMessage = async (userId: string, { mid }: { mid: IMessage['_i
});
}

if (!(await canAccessRoomIdAsync(message.rid, userId))) {
if (!(await canAccessRoomIdAsync(message.rid, user._id))) {
throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'followMessage' });
}

const id = message.tmid || message._id;

const followResult = await follow({ tmid: id, uid: userId });
const followResult = await follow({ tmid: id, uid: user._id });

void notifyOnMessageChange({
id,
});

const isFollowed = true;
await Apps.self?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed);
await Apps.self?.triggerEvent(AppEvents.IPostMessageFollowed, message, user, isFollowed);

return followResult;
};
Expand All @@ -52,12 +52,12 @@ Meteor.methods<ServerMethods>({
async followMessage({ mid }) {
check(mid, String);

const uid = Meteor.userId();
if (!uid) {
const user = (await Meteor.userAsync()) as IUser;
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'followMessage' });
}

return followMessage(uid, { mid });
return followMessage(user, { mid });
},
});

Expand Down
Loading
Loading