Skip to content

Commit 3a17d35

Browse files
committed
test: ensure accept/reject actions can only be performed by the invited user
1 parent 0d14b19 commit 3a17d35

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

apps/meteor/tests/data/rooms.helper.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ export const acceptRoomInvite = (roomId: IRoom['_id'], config?: IRequestConfig)
440440
const requestInstance = config?.request || request;
441441
const credentialsInstance = config?.credentials || credentials;
442442

443-
return new Promise<{ success: boolean }>((resolve) => {
443+
return new Promise<{ success: boolean; error?: string }>((resolve) => {
444444
void requestInstance
445445
.post(api('rooms.invite'))
446446
.set(credentialsInstance)
@@ -453,3 +453,32 @@ export const acceptRoomInvite = (roomId: IRoom['_id'], config?: IRequestConfig)
453453
});
454454
});
455455
};
456+
457+
/**
458+
* Rejects a room invite for the authenticated user.
459+
*
460+
* Processes a room invitation by rejecting it, which prevents the user
461+
* from joining the room and removes them from the invited members list.
462+
* This is essential for federated room workflows where users can decline invitations.
463+
*
464+
* @param roomId - The unique identifier of the room
465+
* @param config - Optional request configuration for custom domains
466+
* @returns Promise resolving to the rejection response
467+
*/
468+
export const rejectRoomInvite = (roomId: IRoom['_id'], config?: IRequestConfig) => {
469+
const requestInstance = config?.request || request;
470+
const credentialsInstance = config?.credentials || credentials;
471+
472+
return new Promise<{ success: boolean; error?: string }>((resolve) => {
473+
void requestInstance
474+
.post(api('rooms.invite'))
475+
.set(credentialsInstance)
476+
.send({
477+
roomId,
478+
action: 'reject',
479+
})
480+
.end((_err: any, req: any) => {
481+
resolve(req.body);
482+
});
483+
});
484+
};

ee/packages/federation-matrix/src/FederationMatrix.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
896896
async handleInvite(roomId: IRoom['_id'], userId: IUser['_id'], action: 'accept' | 'reject'): Promise<void> {
897897
const subscription = await Subscriptions.findInvitedSubscription(roomId, userId);
898898
if (!subscription) {
899-
throw new Error('User does not have a pending invite for this room');
899+
throw new Error('No subscription found or user does not have permission to accept or reject this invite');
900900
}
901901

902902
const room = await Rooms.findOneById(roomId);

ee/packages/federation-matrix/tests/end-to-end/room.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
addUserToRoom,
99
addUserToRoomSlashCommand,
1010
acceptRoomInvite,
11+
rejectRoomInvite,
1112
} from '../../../../../apps/meteor/tests/data/rooms.helper';
1213
import { type IRequestConfig, getRequestConfig, createUser, deleteUser } from '../../../../../apps/meteor/tests/data/users.helper';
1314
import { IS_EE } from '../../../../../apps/meteor/tests/e2e/config/constants';
@@ -1532,5 +1533,46 @@ import { SynapseClient } from '../helper/synapse-client';
15321533
});
15331534
});
15341535
});
1536+
1537+
describe('Accept/Reject invitation permissions', () => {
1538+
describe('User tries to accept another user invitation', () => {
1539+
let channelName: string;
1540+
let federatedChannel: any;
1541+
1542+
beforeAll(async () => {
1543+
channelName = `federated-channel-accept-permission-${Date.now()}`;
1544+
const createResponse = await createRoom({
1545+
type: 'p',
1546+
name: channelName,
1547+
members: [federationConfig.rc1.additionalUser1.username],
1548+
extraData: {
1549+
federated: true,
1550+
},
1551+
config: rc1AdminRequestConfig,
1552+
});
1553+
1554+
federatedChannel = createResponse.body.group;
1555+
1556+
expect(federatedChannel).toHaveProperty('_id');
1557+
expect(federatedChannel).toHaveProperty('name', channelName);
1558+
expect(federatedChannel).toHaveProperty('t', 'p');
1559+
expect(federatedChannel).toHaveProperty('federated', true);
1560+
}, 10000);
1561+
1562+
it('It should not allow admin to accept invitation on behalf of another user', async () => {
1563+
// RC view: Admin tries to accept rc1User1's invitation
1564+
const response = await acceptRoomInvite(federatedChannel._id, rc1AdminRequestConfig);
1565+
expect(response.success).toBe(false);
1566+
expect(response.error).toBe('Failed to handle invite: No subscription found or user does not have permission to accept or reject this invite');
1567+
});
1568+
1569+
it('It should not allow admin to reject invitation on behalf of another user', async () => {
1570+
// RC view: Admin tries to reject rc1User1's invitation
1571+
const response = await rejectRoomInvite(federatedChannel._id, rc1AdminRequestConfig);
1572+
expect(response.success).toBe(false);
1573+
expect(response.error).toBe('Failed to handle invite: No subscription found or user does not have permission to accept or reject this invite');
1574+
});
1575+
});
1576+
});
15351577
});
15361578
});

0 commit comments

Comments
 (0)