Skip to content

Commit

Permalink
Merge branch 'develop' into fix/fileUploadValidation
Browse files Browse the repository at this point in the history
  • Loading branch information
hugocostadev authored Jun 13, 2024
2 parents ab27344 + afa560d commit b7e8cd5
Show file tree
Hide file tree
Showing 40 changed files with 350 additions and 96 deletions.
6 changes: 6 additions & 0 deletions .changeset/famous-scissors-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/i18n": patch
---

Prevent usage of OTR messages with End-to-end Encryption, both feature shouldn't and can't work together.
2 changes: 1 addition & 1 deletion apps/meteor/.meteor/release
Original file line number Diff line number Diff line change
@@ -1 +1 @@
METEOR@2.15
METEOR@2.16
26 changes: 13 additions & 13 deletions apps/meteor/.meteor/versions
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[email protected].10
[email protected].11
[email protected]
[email protected]
[email protected]
[email protected]
[email protected].3
[email protected].4
[email protected]
[email protected]
[email protected]
Expand All @@ -15,14 +15,14 @@ [email protected]
[email protected]
[email protected]
[email protected]
check@1.3.2
check@1.4.1
[email protected]
[email protected]
[email protected]
[email protected].1
[email protected].0
[email protected].2
[email protected].1
[email protected]
[email protected].0
[email protected].1
[email protected]
dispatch:[email protected]
[email protected]
Expand All @@ -31,7 +31,7 @@ [email protected]
[email protected]
[email protected]
[email protected]
[email protected].5
[email protected].6
[email protected]
[email protected]
[email protected]
Expand All @@ -45,17 +45,17 @@ [email protected]
[email protected]
kadira:[email protected]
[email protected]
[email protected].3
[email protected].4
[email protected]
[email protected]
[email protected]
meteorhacks:[email protected]
[email protected]
[email protected].3
[email protected].4
[email protected]
[email protected]
[email protected]
[email protected].8
[email protected].10
[email protected]
[email protected]
[email protected]
Expand Down Expand Up @@ -84,7 +84,7 @@ rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
[email protected]
[email protected].3
[email protected].4
[email protected]
[email protected]
[email protected]
Expand All @@ -93,10 +93,10 @@ [email protected]
[email protected]
[email protected]
[email protected]
[email protected].0
[email protected].1
[email protected]
[email protected]
[email protected]
zodern:[email protected]
zodern:[email protected]
zodern:[email protected].11
zodern:[email protected].13
122 changes: 122 additions & 0 deletions apps/meteor/client/hooks/roomActions/useE2EERoomAction.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useSetting, usePermission, useEndpoint } from '@rocket.chat/ui-contexts';
import { act, renderHook } from '@testing-library/react-hooks';

import { E2EEState } from '../../../app/e2e/client/E2EEState';
import { e2e } from '../../../app/e2e/client/rocketchat.e2e';
import { OtrRoomState } from '../../../app/otr/lib/OtrRoomState';
import { dispatchToastMessage } from '../../lib/toast';
import { useRoom, useRoomSubscription } from '../../views/room/contexts/RoomContext';
import { useE2EEState } from '../../views/room/hooks/useE2EEState';
import { useOTR } from '../useOTR';
import { useE2EERoomAction } from './useE2EERoomAction';

jest.mock('@rocket.chat/ui-contexts', () => ({
useSetting: jest.fn(),
usePermission: jest.fn(),
useEndpoint: jest.fn(),
}));

jest.mock('../../lib/toast', () => ({
dispatchToastMessage: jest.fn(),
}));

jest.mock('../../views/room/contexts/RoomContext', () => ({
useRoom: jest.fn(),
useRoomSubscription: jest.fn(),
}));

jest.mock('../useOTR', () => ({
useOTR: jest.fn(),
}));

jest.mock('../../../app/e2e/client/rocketchat.e2e', () => ({
e2e: {
isReady: jest.fn(),
},
}));

jest.mock('../../views/room/hooks/useE2EEState', () => ({
useE2EEState: jest.fn(),
}));

jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));

jest.mock('meteor/tracker', () => ({
Tracker: {
autorun: jest.fn(),
},
}));

describe('useE2EERoomAction', () => {
const mockRoom = { _id: 'roomId', encrypted: false, t: 'd', name: 'Test Room' };
const mockSubscription = { autoTranslate: false };

beforeEach(() => {
(useSetting as jest.Mock).mockReturnValue(true);
(useRoom as jest.Mock).mockReturnValue(mockRoom);
(useRoomSubscription as jest.Mock).mockReturnValue(mockSubscription);
(useE2EEState as jest.Mock).mockReturnValue(E2EEState.READY);
(usePermission as jest.Mock).mockReturnValue(true);
(useEndpoint as jest.Mock).mockReturnValue(jest.fn().mockResolvedValue({ success: true }));
(e2e.isReady as jest.Mock).mockReturnValue(true);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should dispatch error toast message when otrState is ESTABLISHED', async () => {
(useOTR as jest.Mock).mockReturnValue({ otrState: OtrRoomState.ESTABLISHED });

const { result } = renderHook(() => useE2EERoomAction());

await act(async () => {
await result?.current?.action?.();
});

expect(dispatchToastMessage).toHaveBeenCalledWith({ type: 'error', message: 'E2EE_not_available_OTR' });
});

it('should dispatch error toast message when otrState is ESTABLISHING', async () => {
(useOTR as jest.Mock).mockReturnValue({ otrState: OtrRoomState.ESTABLISHING });

const { result } = renderHook(() => useE2EERoomAction());

await act(async () => {
await result?.current?.action?.();
});

expect(dispatchToastMessage).toHaveBeenCalledWith({ type: 'error', message: 'E2EE_not_available_OTR' });
});

it('should dispatch error toast message when otrState is REQUESTED', async () => {
(useOTR as jest.Mock).mockReturnValue({ otrState: OtrRoomState.REQUESTED });

const { result } = renderHook(() => useE2EERoomAction());

await act(async () => {
await result?.current?.action?.();
});

expect(dispatchToastMessage).toHaveBeenCalledWith({ type: 'error', message: 'E2EE_not_available_OTR' });
});

it('should dispatch success toast message when encryption is enabled', async () => {
(useOTR as jest.Mock).mockReturnValue({ otrState: OtrRoomState.NOT_STARTED });

const { result } = renderHook(() => useE2EERoomAction());

await act(async () => {
await result?.current?.action?.();
});

expect(dispatchToastMessage).toHaveBeenCalledWith({
type: 'success',
message: 'E2E_Encryption_enabled_for_room',
});
});
});
13 changes: 11 additions & 2 deletions apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { isRoomFederated } from '@rocket.chat/core-typings';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useSetting, usePermission, useEndpoint } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import { E2EEState } from '../../../app/e2e/client/E2EEState';
import { OtrRoomState } from '../../../app/otr/lib/OtrRoomState';
import { dispatchToastMessage } from '../../lib/toast';
import { useRoom, useRoomSubscription } from '../../views/room/contexts/RoomContext';
import type { RoomToolboxActionConfig } from '../../views/room/contexts/RoomToolboxContext';
import { useE2EEState } from '../../views/room/hooks/useE2EEState';
import { useOTR } from '../useOTR';

export const useE2EERoomAction = () => {
const enabled = useSetting('E2E_Enable', false);
Expand All @@ -22,10 +24,17 @@ export const useE2EERoomAction = () => {
const permitted = (room.t === 'd' || (permittedToEditRoom && permittedToToggleEncryption)) && readyToEncrypt;
const federated = isRoomFederated(room);
const { t } = useTranslation();
const { otrState } = useOTR();

const toggleE2E = useEndpoint('POST', '/v1/rooms.saveRoomSettings');

const action = useMutableCallback(async () => {
const action = useEffectEvent(async () => {
if (otrState === OtrRoomState.ESTABLISHED || otrState === OtrRoomState.ESTABLISHING || otrState === OtrRoomState.REQUESTED) {
dispatchToastMessage({ type: 'error', message: t('E2EE_not_available_OTR') });

return;
}

const { success } = await toggleE2E({ rid: room._id, encrypted: !room.encrypted });
if (!success) {
return;
Expand Down
70 changes: 70 additions & 0 deletions apps/meteor/client/hooks/useOTR.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useUserId } from '@rocket.chat/ui-contexts';
import { renderHook } from '@testing-library/react-hooks';

import OTR from '../../app/otr/client/OTR';
import { OtrRoomState } from '../../app/otr/lib/OtrRoomState';
import { useRoom } from '../views/room/contexts/RoomContext';
import { useOTR } from './useOTR';

jest.mock('@rocket.chat/ui-contexts', () => ({
useUserId: jest.fn(),
}));

jest.mock('../views/room/contexts/RoomContext', () => ({
useRoom: jest.fn(),
}));

jest.mock('../../app/otr/client/OTR', () => ({
getInstanceByRoomId: jest.fn(),
}));

jest.mock('./useReactiveValue', () => ({
useReactiveValue: jest.fn((fn) => fn()),
}));

describe('useOTR', () => {
it('should return error state when user ID is not available', () => {
(useUserId as jest.Mock).mockReturnValue(undefined);
(useRoom as jest.Mock).mockReturnValue({ _id: 'roomId' });

const { result } = renderHook(() => useOTR());

expect(result.current.otr).toBeUndefined();
expect(result.current.otrState).toBe(OtrRoomState.ERROR);
});

it('should return error state when room ID is not available', () => {
(useUserId as jest.Mock).mockReturnValue('userId');
(useRoom as jest.Mock).mockReturnValue(undefined);

const { result } = renderHook(() => useOTR());

expect(result.current.otr).toBeUndefined();
expect(result.current.otrState).toBe(OtrRoomState.ERROR);
});

it('should return error state when OTR instance is not available', () => {
(useUserId as jest.Mock).mockReturnValue('userId');
(useRoom as jest.Mock).mockReturnValue({ _id: 'roomId' });
(OTR.getInstanceByRoomId as jest.Mock).mockReturnValue(undefined);

const { result } = renderHook(() => useOTR());

expect(result.current.otr).toBeUndefined();
expect(result.current.otrState).toBe(OtrRoomState.ERROR);
});

it('should return the correct OTR instance and state when available', () => {
const mockOtrInstance = {
getState: jest.fn().mockReturnValue(OtrRoomState.NOT_STARTED),
};
(useUserId as jest.Mock).mockReturnValue('userId');
(useRoom as jest.Mock).mockReturnValue({ _id: 'roomId' });
(OTR.getInstanceByRoomId as jest.Mock).mockReturnValue(mockOtrInstance);

const { result } = renderHook(() => useOTR());

expect(result.current.otr).toBe(mockOtrInstance);
expect(result.current.otrState).toBe(OtrRoomState.NOT_STARTED);
});
});
28 changes: 28 additions & 0 deletions apps/meteor/client/hooks/useOTR.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useUserId } from '@rocket.chat/ui-contexts';
import { useMemo, useCallback } from 'react';

import OTR from '../../app/otr/client/OTR';
import type { OTRRoom } from '../../app/otr/client/OTRRoom';
import { OtrRoomState } from '../../app/otr/lib/OtrRoomState';
import { useRoom } from '../views/room/contexts/RoomContext';
import { useReactiveValue } from './useReactiveValue';

export const useOTR = (): { otr: OTRRoom | undefined; otrState: OtrRoomState } => {
const uid = useUserId();
const room = useRoom();

const otr = useMemo(() => {
if (!uid || !room) {
return;
}

return OTR.getInstanceByRoomId(uid, room._id);
}, [uid, room]);

const otrState = useReactiveValue(useCallback(() => (otr ? otr.getState() : OtrRoomState.ERROR), [otr]));

return {
otr,
otrState,
};
};
Loading

0 comments on commit b7e8cd5

Please sign in to comment.