Skip to content

Commit 7df7103

Browse files
committed
chore: remove comments and improve dm detection
1 parent 39897b3 commit 7df7103

4 files changed

Lines changed: 83 additions & 17 deletions

File tree

apps/meteor/server/api/v1/rooms.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import { FileUpload } from '../../lib/media/file-upload';
6161
import { notifyOnSubscriptionChanged } from '../../lib/notifyListener';
6262
import { openRoom } from '../../lib/openRoom';
6363
import type { RoomRoles } from '../../lib/roles/getRoomRoles';
64-
import { parseDirectRoomTargets } from '../../lib/rooms/findDirectRoomByIdentifier';
64+
import { resolveDirectRoomTargets } from '../../lib/rooms/findDirectRoomByIdentifier';
6565
import { syncRolePrioritiesForRoomIfRequired } from '../../lib/rooms/syncRolePrioritiesForRoomIfRequired';
6666
import { unbanUserFromRoom } from '../../lib/unbanUserFromRoom';
6767
import { createDirectMessage } from '../../meteor-methods/messages/createDirectMessage';
@@ -523,9 +523,6 @@ API.v1.post(
523523
'rooms.getOrCreate',
524524
{
525525
authRequired: false,
526-
// Opting out, not omitting: the limiter keys on IP, so a cap here would be shared by every user
527-
// behind the same egress address, and omitting this inherits the 10/min default. Still open:
528-
// this route can create rooms, so the right guard is likely per-user on the create branch.
529526
rateLimiterOptions: false,
530527
body: ajv.compile<{ type: RoomType; name: string }>({
531528
type: 'object',
@@ -564,7 +561,12 @@ API.v1.post(
564561
return API.v1.failure('Invalid room [error-invalid-room]', 'error-invalid-room');
565562
}
566563

567-
const { rid } = await createDirectMessage(parseDirectRoomTargets(name), this.userId);
564+
const targets = await resolveDirectRoomTargets(name);
565+
if (!targets) {
566+
return API.v1.failure('Invalid room [error-invalid-room]', 'error-invalid-room');
567+
}
568+
569+
const { rid } = await createDirectMessage(targets, this.userId);
568570

569571
const created = await findRoomByTypeAndName(this.userId ?? null, type, rid);
570572
if (!created) {

apps/meteor/server/lib/rooms/findDirectRoomByIdentifier.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import type { IRoom, IUser } from '@rocket.chat/core-typings';
22
import { Rooms, Users } from '@rocket.chat/models';
33

4-
// `/direct/:rid` carries either a room id or the participants themselves: one username for a
5-
// regular DM, a comma separated list for a group one. Lookup and creation must read it the same
6-
// way, so both go through here.
74
export const parseDirectRoomTargets = (identifier: string): string[] => identifier.split(',').map((username) => username.trim());
85

9-
// Direct rooms carry no `name`; the member set is what identifies them, and it is the same
10-
// primitive `createDirectRoom` resolves against.
6+
const resolveUsernames = async (usernames: string[]): Promise<Pick<IUser, '_id' | 'username'>[] | null> => {
7+
const users = await Users.findUsersByUsernames<Pick<IUser, '_id' | 'username'>>(usernames, {
8+
projection: { _id: 1, username: 1 },
9+
}).toArray();
10+
11+
return users.length === usernames.length ? users : null;
12+
};
13+
14+
export const resolveDirectRoomTargets = async (identifier: string): Promise<string[] | null> => {
15+
const targets = [...new Set(parseDirectRoomTargets(identifier))];
16+
17+
return (await resolveUsernames(targets)) ? targets : null;
18+
};
19+
1120
export const findDirectRoomByIdentifier = async (identifier: string, user: Pick<IUser, '_id' | 'username'>): Promise<IRoom | null> => {
1221
const targets = parseDirectRoomTargets(identifier);
1322

@@ -22,9 +31,8 @@ export const findDirectRoomByIdentifier = async (identifier: string, user: Pick<
2231
return null;
2332
}
2433

25-
const usernames = [...new Set([user.username, ...targets])];
26-
const members = await Users.findUsersByUsernames(usernames, { projection: { _id: 1 } }).toArray();
27-
if (members.length !== usernames.length) {
34+
const members = await resolveUsernames([...new Set([user.username, ...targets])]);
35+
if (!members) {
2836
return null;
2937
}
3038

apps/meteor/tests/end-to-end/api/rooms.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
import { assignRoleToUser, createCustomRole, deleteCustomRole } from '../../data/roles.helper';
3333
import { createRoom, deleteRoom } from '../../data/rooms.helper';
3434
import { createTeam, deleteTeam } from '../../data/teams.helper';
35-
import { password } from '../../data/user';
35+
import { adminUsername, password } from '../../data/user';
3636
import type { TestUser } from '../../data/users.helper';
3737
import { createUser, deleteUser, login } from '../../data/users.helper';
3838
import { IS_EE } from '../../e2e/config/constants';
@@ -138,6 +138,35 @@ describe('[Rooms]', () => {
138138
expect(res.body.room).to.have.property('_id', publicChannel._id);
139139
});
140140

141+
it('should create the self-DM when the caller addresses their own username', async () => {
142+
const res = await request.post(api('rooms.getOrCreate')).set(credentials).send({ type: 'd', name: adminUsername }).expect(200);
143+
144+
expect(res.body.room).to.have.property('t', 'd');
145+
expect(res.body.room).to.have.property('usersCount', 1);
146+
});
147+
148+
it('should fail when a direct message target does not exist, instead of creating a smaller room', async () => {
149+
const res = await request
150+
.post(api('rooms.getOrCreate'))
151+
.set(credentials)
152+
.send({ type: 'd', name: `ghost-${Date.now()}` })
153+
.expect(400);
154+
155+
expect(res.body).to.have.property('success', false);
156+
expect(res.body).to.have.property('errorType', 'error-invalid-room');
157+
});
158+
159+
it('should fail when one of the group direct message targets does not exist', async () => {
160+
const res = await request
161+
.post(api('rooms.getOrCreate'))
162+
.set(credentials)
163+
.send({ type: 'd', name: `${dmTarget.username},ghost-${Date.now()}` })
164+
.expect(400);
165+
166+
expect(res.body).to.have.property('success', false);
167+
expect(res.body).to.have.property('errorType', 'error-invalid-room');
168+
});
169+
141170
it('should fail for a channel that does not exist, since only DMs can be created on demand', async () => {
142171
const res = await request
143172
.post(api('rooms.getOrCreate'))

apps/meteor/tests/unit/server/lib/rooms/findDirectRoomByIdentifier.spec.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ const UsersStub = {
1212
findUsersByUsernames: Sinon.stub(),
1313
};
1414

15-
const { findDirectRoomByIdentifier } = proxyquire.noCallThru().load('../../../../../server/lib/rooms/findDirectRoomByIdentifier.ts', {
16-
'@rocket.chat/models': { Rooms: RoomsStub, Users: UsersStub },
17-
});
15+
const { findDirectRoomByIdentifier, resolveDirectRoomTargets } = proxyquire
16+
.noCallThru()
17+
.load('../../../../../server/lib/rooms/findDirectRoomByIdentifier.ts', {
18+
'@rocket.chat/models': { Rooms: RoomsStub, Users: UsersStub },
19+
});
1820

1921
const cursorOf = (docs: unknown[]) => ({ toArray: async () => docs });
2022

@@ -102,3 +104,28 @@ describe('findDirectRoomByIdentifier', () => {
102104
expect(await findDirectRoomByIdentifier('alice', { _id: 'me' })).to.be.null;
103105
});
104106
});
107+
108+
describe('resolveDirectRoomTargets', () => {
109+
beforeEach(() => {
110+
UsersStub.findUsersByUsernames.reset();
111+
});
112+
113+
it('should return the targets when every username resolves to a user', async () => {
114+
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'a' }, { _id: 'b' }]));
115+
116+
expect(await resolveDirectRoomTargets('a, b')).to.deep.equal(['a', 'b']);
117+
});
118+
119+
it('should return null when any username does not resolve to a user', async () => {
120+
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'a' }]));
121+
122+
expect(await resolveDirectRoomTargets('a,ghost')).to.be.null;
123+
});
124+
125+
it('should not ask for the same username twice', async () => {
126+
UsersStub.findUsersByUsernames.returns(cursorOf([{ _id: 'a' }]));
127+
128+
expect(await resolveDirectRoomTargets('a,a')).to.deep.equal(['a']);
129+
expect(UsersStub.findUsersByUsernames.firstCall.args[0]).to.deep.equal(['a']);
130+
});
131+
});

0 commit comments

Comments
 (0)