Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: show only relevant userInfoActions for mentioned non-members #31525

Merged
merged 56 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
12a9094
added findByUsernameAndRoom to User model
abhinavkrin Jan 24, 2024
65f0e0e
added groups/channels/im membercheck endpoints
abhinavkrin Jan 24, 2024
432d554
show only relevant userInfoActions for non-members
abhinavkrin Jan 24, 2024
2a58c44
added changeset
abhinavkrin Jan 24, 2024
f848c67
added ajv validation
abhinavkrin Feb 1, 2024
aa8aaab
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 1, 2024
8b07031
remove check()
abhinavkrin Feb 1, 2024
c1580f4
replaced memberExists endpoint with subscription.exists
abhinavkrin Feb 7, 2024
0dc3079
use subscriptions.exist endpoint
abhinavkrin Feb 7, 2024
d78888b
Update packages/rest-typings/src/v1/dm/im.ts
abhinavkrin Feb 16, 2024
9e42919
Update apps/meteor/app/api/server/v1/subscriptions.ts
abhinavkrin Feb 16, 2024
e126fc8
Update apps/meteor/app/api/server/v1/subscriptions.ts
abhinavkrin Feb 16, 2024
9abf6c2
Update packages/rest-typings/src/v1/subscriptionsEndpoints.ts
abhinavkrin Feb 16, 2024
5143108
Update apps/meteor/client/views/hooks/useMemberExists.ts
abhinavkrin Feb 16, 2024
fafbaff
minor changes and subscriptions.exists -> rooms.isMember
abhinavkrin Feb 16, 2024
b663025
unit test for rooms.isMember
abhinavkrin Feb 16, 2024
c828dd9
use api endpoint
abhinavkrin Feb 17, 2024
0cc0471
improve api test
abhinavkrin Feb 17, 2024
ecf3cc1
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 17, 2024
8aa68fc
updated message for add to room userinfo action
abhinavkrin Feb 19, 2024
4327cf2
Merge branch 'develop' into fix/mention-user-membership-check
scuciatto Feb 20, 2024
db52395
added tests
abhinavkrin Feb 21, 2024
8c363e2
updated rooms.isMember endpoint and its test
abhinavkrin Feb 21, 2024
cb61119
updated test 09-rooms.js
abhinavkrin Feb 22, 2024
5386bf4
Update useMemberExists.ts
abhinavkrin Feb 22, 2024
3db764d
minor fixes
abhinavkrin Feb 23, 2024
5f9e7f5
minor fixes
abhinavkrin Feb 23, 2024
8e9b4b7
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 23, 2024
68b2bb1
minor fixes
abhinavkrin Feb 23, 2024
50cd2c0
Update apps/meteor/tests/end-to-end/api/09-rooms.js
abhinavkrin Feb 27, 2024
dfe9eb8
fixed test 09-rooms.js
abhinavkrin Feb 27, 2024
eac77f2
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Feb 27, 2024
dde4c9a
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into fix/…
abhinavkrin Mar 1, 2024
eb86620
Merge branch 'develop' into fix/mention-user-membership-check
matheusbsilva137 Mar 4, 2024
7d142b1
improved useMemberExists hook
abhinavkrin Mar 13, 2024
76a71ee
updated useMemberExists
abhinavkrin Mar 14, 2024
d3f53ff
fix review
yash-rajpal Mar 15, 2024
910b67c
minor fix
abhinavkrin Mar 15, 2024
5b2b67e
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Mar 19, 2024
172ad7a
fixed loading to reflect ui
abhinavkrin Mar 27, 2024
5812bad
merge develop
abhinavkrin Jun 12, 2024
8e7de27
added ui e2e tests
abhinavkrin Jun 13, 2024
852d304
improved tests
abhinavkrin Jun 13, 2024
5c383ec
minor refactor
abhinavkrin Jun 18, 2024
c751576
Merge branch 'develop' into fix/mention-user-membership-check
d-gubert Jun 18, 2024
5f8bb47
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 19, 2024
9e17053
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 19, 2024
2beeb77
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 20, 2024
10483e2
Merge branch 'develop' into fix/mention-user-membership-check
abhinavkrin Jun 21, 2024
b68f6ab
minor fixes and improved tests
abhinavkrin Jun 21, 2024
1cee4b2
modified rooms.isMember response
abhinavkrin Jun 25, 2024
a2176ad
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into fix/…
abhinavkrin Jun 25, 2024
01b1a69
fixed types
abhinavkrin Jun 25, 2024
fdad230
fix typo
abhinavkrin Jun 28, 2024
ee18257
merge develop
abhinavkrin Jul 18, 2024
22bd817
Merge branch 'develop' into fix/mention-user-membership-check
KevLehman Aug 23, 2024
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
6 changes: 6 additions & 0 deletions .changeset/ninety-hounds-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/rest-typings': patch
'@rocket.chat/meteor': patch
---

Fix: Show correct user info actions for non-members in channels.
38 changes: 37 additions & 1 deletion apps/meteor/app/api/server/v1/subscriptions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Subscriptions } from '@rocket.chat/models';
import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
import {
isSubscriptionsGetProps,
isSubscriptionsGetOneProps,
isSubscriptionsReadProps,
isSubscriptionsUnreadProps,
} from '@rocket.chat/rest-typings';
import { isSubscriptionsExistsProps } from '@rocket.chat/rest-typings/src';
import { Meteor } from 'meteor/meteor';

import { readMessages } from '../../../../server/lib/readMessages';
import { canAccessRoomAsync } from '../../../authorization/server';
import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { API } from '../api';

API.v1.addRoute(
Expand Down Expand Up @@ -103,3 +107,35 @@ API.v1.addRoute(
},
},
);

API.v1.addRoute(
'subscriptions.exists',
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
{
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
authRequired: true,
validateParams: isSubscriptionsExistsProps,
},
{
async get() {
const { username, roomId, userId, roomName } = this.queryParams;
const [room, user] = await Promise.all([
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
Rooms.findOneByIdOrName(roomId || roomName),
Users.findOneByIdOrUsername(userId || username),
]);
if (!room?._id) {
return API.v1.failure('error-room-not-found');
}
if (!user?._id) {
return API.v1.failure('error-user-not-found');
}

if (room.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', room._id))) {
return API.v1.unauthorized();
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
}

if ((await canAccessRoomAsync(room, this.user)) || (await canAccessRoomIdAsync(room._id, this.userId))) {
return API.v1.success({ exists: !!(await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) });
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
}
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
return API.v1.unauthorized();
},
},
);
16 changes: 16 additions & 0 deletions apps/meteor/client/views/hooks/useMemberExists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';

type MembersExistsOptions = {
rid: string;
username: string;
refresh?: boolean;
};

export const useMemberExists = (options: MembersExistsOptions) => {
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
const checkMember = useEndpoint('GET', '/v1/subscriptions.exists');

return useQuery(['roomMembershipCheck', options.rid, options.username], () =>
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
checkMember({ roomId: options.rid, username: options.username }),
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
import { isRoomFederated } from '@rocket.chat/core-typings';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import {
useTranslation,
useUser,
useUserRoom,
useUserSubscription,
useMethod,
useToastMessageDispatch,
useAtLeastOnePermission,
} from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';

import * as Federation from '../../../../../lib/federation/Federation';
import { useAddMatrixUsers } from '../../../contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers';
import { getRoomDirectives } from '../../../lib/getRoomDirectives';
import type { UserInfoAction } from '../useUserInfoActions';

export const useAddUserAction = (
user: Pick<IUser, '_id' | 'username'>,
rid: IRoom['_id'],
reload?: () => void,
): UserInfoAction | undefined => {
const t = useTranslation();
const room = useUserRoom(rid);
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
const currentUser = useUser();
const subscription = useUserSubscription(rid);
const dispatchToastMessage = useToastMessageDispatch();

const { username, _id: uid } = user;

if (!room) {
throw Error('Room not provided');
}

const hasPermissionToAddUsers = useAtLeastOnePermission(
useMemo(() => [room?.t === 'p' ? 'add-user-to-any-p-room' : 'add-user-to-any-c-room', 'add-user-to-joined-room'], [room?.t]),
rid,
);

const userCanAdd =
room && user && isRoomFederated(room)
? Federation.isEditableByTheUser(currentUser || undefined, room, subscription)
: hasPermissionToAddUsers;

const { roomCanInvite } = getRoomDirectives({ room, showingUserId: uid, userSubscription: subscription });

const addUsersToRooms = useMethod('addUsersToRoom');
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved

const handleAddUser = useEffectEvent(async ({ users }) => {
await addUsersToRooms({ rid, users });
reload?.();
});

const addClickHandler = useAddMatrixUsers();

const addUserOptionAction = useEffectEvent(async () => {
try {
const users = [username as string];
if (isRoomFederated(room)) {
addClickHandler.mutate({
users,
handleSave: handleAddUser,
});
} else {
await handleAddUser({ users });
}
dispatchToastMessage({ type: 'success', message: t('User_added') });
} catch (error) {
dispatchToastMessage({ type: 'error', message: error as Error });
}
});

const addUserOption = useMemo(
() =>
roomCanInvite && userCanAdd
? {
content: t('Add_User'),
icon: 'user-plus' as const,
onClick: addUserOptionAction,
type: 'management' as const,
}
: undefined,
[roomCanInvite, userCanAdd, t, addUserOptionAction],
);

return addUserOption;
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings';
import type { Icon } from '@rocket.chat/fuselage';
import { useLayoutHiddenActions } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';

import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem';
import { useEmbeddedLayout } from '../../../../hooks/useEmbeddedLayout';
import { useMemberExists } from '../../../hooks/useMemberExists';
import { useAddUserAction } from './actions/useAddUserAction';
import { useBlockUserAction } from './actions/useBlockUserAction';
import { useCallAction } from './actions/useCallAction';
import { useChangeLeaderAction } from './actions/useChangeLeaderAction';
Expand Down Expand Up @@ -40,6 +42,14 @@ export const useUserInfoActions = (
reload?: () => void,
size = 2,
): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => {
const { data, refetch } = useMemberExists(useMemo(() => ({ rid, username: user.username || '' }), [rid, user.username]));
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
const memberChangeReload = useCallback(async () => {
await reload?.();
await refetch();
}, [reload, refetch]);
const isMember = !!useMemo(() => data?.exists, [data?.exists]);
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved

const addUser = useAddUserAction(user, rid, memberChangeReload);
const blockUser = useBlockUserAction(user, rid);
const changeLeader = useChangeLeaderAction(user, rid);
const changeModerator = useChangeModeratorAction(user, rid);
Expand All @@ -48,7 +58,7 @@ export const useUserInfoActions = (
const openDirectMessage = useDirectMessageAction(user, rid);
const ignoreUser = useIgnoreUserAction(user, rid);
const muteUser = useMuteUserAction(user, rid);
const removeUser = useRemoveUserAction(user, rid, reload);
const removeUser = useRemoveUserAction(user, rid, memberChangeReload);
const call = useCallAction(user);
const reportUserOption = useReportUser(user);
const isLayoutEmbedded = useEmbeddedLayout();
Expand All @@ -58,15 +68,16 @@ export const useUserInfoActions = (
() => ({
...(openDirectMessage && !isLayoutEmbedded && { openDirectMessage }),
...(call && { call }),
...(changeOwner && { changeOwner }),
...(changeLeader && { changeLeader }),
...(changeModerator && { changeModerator }),
...(openModerationConsole && { openModerationConsole }),
...(ignoreUser && { ignoreUser }),
...(muteUser && { muteUser }),
...(!isMember && addUser && { addUser }),
...(isMember && changeOwner && { changeOwner }),
...(isMember && changeLeader && { changeLeader }),
...(isMember && changeModerator && { changeModerator }),
...(isMember && openModerationConsole && { openModerationConsole }),
...(isMember && ignoreUser && { ignoreUser }),
...(isMember && muteUser && { muteUser }),
...(blockUser && { toggleBlock: blockUser }),
...(reportUserOption && { reportUser: reportUserOption }),
...(removeUser && { removeUser }),
...(isMember && removeUser && { removeUser }),
}),
[
openDirectMessage,
Expand All @@ -81,6 +92,8 @@ export const useUserInfoActions = (
removeUser,
reportUserOption,
openModerationConsole,
addUser,
isMember,
],
);

Expand Down
6 changes: 4 additions & 2 deletions apps/meteor/client/views/room/lib/getRoomDirectives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type getRoomDirectiesType = {
roomCanBlock: boolean;
roomCanMute: boolean;
roomCanRemove: boolean;
roomCanInvite: boolean;
};

export const getRoomDirectives = ({
Expand All @@ -24,7 +25,7 @@ export const getRoomDirectives = ({
}): getRoomDirectiesType => {
const roomDirectives = room?.t && roomCoordinator.getRoomDirectives(room.t);

const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove] = [
const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite] = [
...((roomDirectives && [
roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_OWNER, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_LEADER, showingUserId, userSubscription),
Expand All @@ -33,9 +34,10 @@ export const getRoomDirectives = ({
roomDirectives.allowMemberAction(room, RoomMemberActions.BLOCK, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.MUTE, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.REMOVE_USER, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.INVITE, showingUserId, userSubscription),
]) ??
[]),
];

return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove };
return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite };
};
1 change: 1 addition & 0 deletions packages/rest-typings/src/v1/dm/im.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type ImEndpoints = {
members: Pick<IUser, '_id' | 'status' | 'name' | 'username' | 'utcOffset'>[];
}>;
};

abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
'/v1/im.messages': {
GET: (params: DmMessagesProps) => PaginatedResult<{
messages: IMessage[];
Expand Down
48 changes: 48 additions & 0 deletions packages/rest-typings/src/v1/subscriptionsEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type SubscriptionsRead = { rid: IRoom['_id']; readThreads?: boolean } | { roomId

type SubscriptionsUnread = { roomId: IRoom['_id'] } | { firstUnreadMessage: Pick<IMessage, '_id'> };

type SubscriptionsExistsProps = { roomId: string; username: string };
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved

const ajv = new Ajv({
coerceTypes: true,
});
Expand Down Expand Up @@ -109,6 +111,49 @@ const SubscriptionsUnreadSchema = {

export const isSubscriptionsUnreadProps = ajv.compile<SubscriptionsUnread>(SubscriptionsUnreadSchema);

const SubscriptionsExistsPropsSchema = {
oneOf: [
{
type: 'object',
properties: {
roomId: { type: 'string' },
userId: { type: 'string' },
},
required: ['roomId', 'userId'],
additionalProperties: false,
},
{
type: 'object',
properties: {
roomId: { type: 'string' },
username: { type: 'string' },
},
required: ['roomId', 'username'],
additionalProperties: false,
},
{
type: 'object',
properties: {
roomName: { type: 'string' },
userId: { type: 'string' },
},
required: ['roomName', 'userId'],
additionalProperties: false,
},
{
type: 'object',
properties: {
roomName: { type: 'string' },
username: { type: 'string' },
},
required: ['roomName', 'username'],
additionalProperties: false,
},
],
abhinavkrin marked this conversation as resolved.
Show resolved Hide resolved
};

export const isSubscriptionsExistsProps = ajv.compile<SubscriptionsExistsProps>(SubscriptionsExistsPropsSchema);

export type SubscriptionsEndpoints = {
'/v1/subscriptions.get': {
GET: (params: SubscriptionsGet) => {
Expand All @@ -130,4 +175,7 @@ export type SubscriptionsEndpoints = {
'/v1/subscriptions.unread': {
POST: (params: SubscriptionsUnread) => void;
};
'/v1/subscriptions.exists': {
GET: (params: SubscriptionsExistsProps) => { exists: boolean };
};
};
Loading