Skip to content

Commit

Permalink
fix: Users without preview permission subscribing to public channel s…
Browse files Browse the repository at this point in the history
…tream (#34922)

Co-authored-by: gabriellsh <[email protected]>
  • Loading branch information
tiagoevanp and gabriellsh authored Jan 17, 2025
1 parent baaee3f commit 3c237b2
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/metal-pets-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/i18n": minor
---

Fixes an issue where users without the "Preview public channel" permission would receive new messages sent to the channel
27 changes: 27 additions & 0 deletions apps/meteor/client/hooks/useJoinRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { sdk } from '../../app/utils/client/lib/SDKClient';

type UseJoinRoomMutationFunctionProps = {
rid: IRoom['_id'];
reference: string;
type: IRoom['t'];
};

export const useJoinRoom = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async ({ rid, reference, type }: UseJoinRoomMutationFunctionProps) => {
await sdk.call('joinRoom', rid);

return { reference, type };
},
onSuccess: (data) => {
queryClient.invalidateQueries({
queryKey: ['rooms', data],
});
},
});
};
15 changes: 15 additions & 0 deletions apps/meteor/client/lib/errors/NotSubscribedToRoomError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { IRoom } from '@rocket.chat/core-typings';

import { RocketChatError } from './RocketChatError';

type NotSubscribedToRoomErrorDetails = { rid: IRoom['_id'] };

export class NotSubscribedToRoomError extends RocketChatError<'not-subscribed-room', NotSubscribedToRoomErrorDetails> {
public declare readonly reason: string;

public declare readonly details: NotSubscribedToRoomErrorDetails;

constructor(message = 'Not subscribed to this room', details: NotSubscribedToRoomErrorDetails) {
super('not-subscribed-room', message, details);
}
}
65 changes: 65 additions & 0 deletions apps/meteor/client/views/room/NotSubscribedRoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Box, States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
import { Header, HeaderToolbar } from '@rocket.chat/ui-client';
import { useLayout } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import { Trans, useTranslation } from 'react-i18next';

import RoomLayout from './layout/RoomLayout';
import SidebarToggler from '../../components/SidebarToggler';
import { useJoinRoom } from '../../hooks/useJoinRoom';

type NotSubscribedRoomProps = {
rid: IRoom['_id'];
reference: string;
type: IRoom['t'];
};

const NotSubscribedRoom = ({ rid, reference, type }: NotSubscribedRoomProps): ReactElement => {
const { t } = useTranslation();

const handleJoinClick = useJoinRoom();
// TODO: Handle onJoinClick error

const { isMobile } = useLayout();

return (
<RoomLayout
header={
isMobile && (
<Header justifyContent='start'>
<HeaderToolbar>
<SidebarToggler />
</HeaderToolbar>
</Header>
)
}
body={
<Box display='flex' justifyContent='center' height='full'>
<States>
<StatesIcon name='add-user' />
<StatesTitle>{t('Channel_not_joined')}</StatesTitle>
<StatesSubtitle>
<Box display='block'>
<Trans
i18nKey='Join_channel_to_view_history'
values={{ channel: reference }}
components={{ b: <Box is='span' fontWeight={600} /> }}
/>
</Box>
</StatesSubtitle>
<Box mbs={16}>
<StatesActions>
<StatesAction disabled={handleJoinClick.isPending} onClick={() => handleJoinClick.mutate({ rid, reference, type })}>
{t('Join_channel')}
</StatesAction>
</StatesActions>
</Box>
</States>
</Box>
}
/>
);
};

export default NotSubscribedRoom;
6 changes: 6 additions & 0 deletions apps/meteor/client/views/room/RoomOpener.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import type { ReactElement } from 'react';
import { lazy, Suspense } from 'react';
import { useTranslation } from 'react-i18next';

import NotSubscribedRoom from './NotSubscribedRoom';
import RoomSkeleton from './RoomSkeleton';
import RoomSidepanel from './Sidepanel/RoomSidepanel';
import { useOpenRoom } from './hooks/useOpenRoom';
import { FeaturePreviewSidePanelNavigation } from '../../components/FeaturePreviewSidePanelNavigation';
import { Header } from '../../components/Header';
import { getErrorMessage } from '../../lib/errorHandling';
import { NotAuthorizedError } from '../../lib/errors/NotAuthorizedError';
import { NotSubscribedToRoomError } from '../../lib/errors/NotSubscribedToRoomError';
import { OldUrlRoomError } from '../../lib/errors/OldUrlRoomError';
import { RoomNotFoundError } from '../../lib/errors/RoomNotFoundError';

Expand Down Expand Up @@ -60,6 +62,10 @@ const RoomOpener = ({ type, reference }: RoomOpenerProps): ReactElement => {
return <RoomNotFound />;
}

if (error instanceof NotSubscribedToRoomError) {
return <NotSubscribedRoom rid={error.details.rid} reference={reference} type={type} />;
}

if (error instanceof NotAuthorizedError) {
return <NotAuthorizedPage />;
}
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/client/views/room/RoomOpenerEmbedded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ReactElement } from 'react';
import { lazy, Suspense, useEffect } from 'react';
import { useTranslation } from 'react-i18next';

import NotSubscribedRoom from './NotSubscribedRoom';
import RoomSkeleton from './RoomSkeleton';
import RoomSidepanel from './Sidepanel/RoomSidepanel';
import { useOpenRoom } from './hooks/useOpenRoom';
Expand All @@ -16,6 +17,7 @@ import { FeaturePreviewSidePanelNavigation } from '../../components/FeaturePrevi
import { Header } from '../../components/Header';
import { getErrorMessage } from '../../lib/errorHandling';
import { NotAuthorizedError } from '../../lib/errors/NotAuthorizedError';
import { NotSubscribedToRoomError } from '../../lib/errors/NotSubscribedToRoomError';
import { OldUrlRoomError } from '../../lib/errors/OldUrlRoomError';
import { RoomNotFoundError } from '../../lib/errors/RoomNotFoundError';

Expand Down Expand Up @@ -106,6 +108,10 @@ const RoomOpenerEmbedded = ({ type, reference }: RoomOpenerProps): ReactElement
return <RoomNotFound />;
}

if (error instanceof NotSubscribedToRoomError) {
return <NotSubscribedRoom rid={error.details.rid} reference={reference} type={type} />;
}

if (error instanceof NotAuthorizedError) {
return <NotAuthorizedPage />;
}
Expand Down
5 changes: 0 additions & 5 deletions apps/meteor/client/views/room/body/RoomBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,6 @@ const RoomBody = (): ReactElement => {
onClick={handleJumpToRecentButtonClick}
text={t('Jump_to_recent_messages')}
/>
{!canPreview ? (
<div className='content room-not-found error-color'>
<div>{t('You_must_join_to_view_messages_in_this_channel')}</div>
</div>
) : null}
<div
className={[
'wrapper',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export const useRoomLeave = (room: IRoom, joined = true) => {
try {
await leaveRoom(room._id);
router.navigate('/home');
LegacyRoomManager.close(room._id);

if (room.name) {
LegacyRoomManager.close(`${room.t}${room.name}`);
}
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
Expand Down
14 changes: 12 additions & 2 deletions apps/meteor/client/views/room/hooks/useOpenRoom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IRoom, RoomType } from '@rocket.chat/core-typings';
import { useMethod, useRoute, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useMethod, usePermission, useRoute, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect, useRef } from 'react';

Expand All @@ -8,11 +8,13 @@ import { Rooms } from '../../../../app/models/client';
import { roomFields } from '../../../../lib/publishFields';
import { omit } from '../../../../lib/utils/omit';
import { NotAuthorizedError } from '../../../lib/errors/NotAuthorizedError';
import { NotSubscribedToRoomError } from '../../../lib/errors/NotSubscribedToRoomError';
import { OldUrlRoomError } from '../../../lib/errors/OldUrlRoomError';
import { RoomNotFoundError } from '../../../lib/errors/RoomNotFoundError';

export function useOpenRoom({ type, reference }: { type: RoomType; reference: string }) {
const user = useUser();
const hasPreviewPermission = usePermission('preview-c-room');
const allowAnonymousRead = useSetting('Accounts_AllowAnonymousRead', true);
const getRoomByTypeAndName = useMethod('getRoomByTypeAndName');
const createDirectMessage = useMethod('createDirectMessage');
Expand Down Expand Up @@ -88,17 +90,25 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st
unsubscribeFromRoomOpenedEvent.current();
unsubscribeFromRoomOpenedEvent.current = RoomManager.once('opened', () => fireGlobalEvent('room-opened', omit(room, 'usernames')));

const sub = Subscriptions.findOne({ rid: room._id });

// if user doesn't exist at this point, anonymous read is enabled, otherwise an error would have been thrown
if (user && !sub && !hasPreviewPermission) {
throw new NotSubscribedToRoomError(undefined, { rid: room._id });
}

LegacyRoomManager.open({ typeName: type + reference, rid: room._id });

if (room._id === RoomManager.opened) {
return { rid: room._id };
}

// update user's room subscription
const sub = Subscriptions.findOne({ rid: room._id });

if (!!user?._id && sub && !sub.open) {
await openRoom.mutateAsync({ roomId: room._id, userId: user._id });
}

return { rid: room._id };
},
retry: 0,
Expand Down
4 changes: 4 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -2972,6 +2972,7 @@
"Join_audio_call": "Join audio call",
"Join_call": "Join call",
"Join_Chat": "Join Chat",
"Join_channel": "Join channel",
"Join_conference": "Join conference",
"Join_default_channels": "Join default channels",
"Join_discussion": "Join discussion",
Expand Down Expand Up @@ -6235,6 +6236,9 @@
"you_are_in_preview_mode_of_incoming_livechat": "You are in preview mode of this chat",
"You_are_logged_in_as": "You are logged in as",
"You_are_not_authorized_to_view_this_page": "You are not authorized to view this page.",
"Channel_not_joined": "Channel not joined",
"Join_channel_to_view_history": "Join <b>{{channel}}</b> to view history.",
"You_need_to_join_this_channel": "You need to join this channel to view its history",
"You_can_change_a_different_avatar_too": "You can override the avatar used to post from this integration.",
"You_can_close_this_window_now": "You can close this window now.",
"You_can_do_from_account_preferences": "You can do this later from your account preferences",
Expand Down

0 comments on commit 3c237b2

Please sign in to comment.