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

Implement server-wide open group user bans and unbans. #2653

Open
wants to merge 4 commits into
base: clearnet
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,17 @@
"blockedSettingsTitle": "Blocked Contacts",
"conversationsSettingsTitle": "Conversations",
"unbanUser": "Unban User",
"serverUnbanUser": "Unban User from Server",
"userUnbanned": "User unbanned successfully",
"userUnbanFailed": "Unban failed!",
"globalUserUnbanFailed": "Unban failed! Are you a global admin/mod?",
"banUser": "Ban User",
"banUserAndDeleteAll": "Ban and Delete All",
"serverBanUser": "Ban User from Server",
"serverBanUserAndDeleteAll": "Ban from Server and Delete All",
"userBanned": "Banned successfully",
"userBanFailed": "Ban failed!",
"GlobalUserBanFailed": "Ban failed! Are you a global admin/mod?",
"leaveGroup": "Leave Group",
"leaveAndRemoveForEveryone": "Leave Group and Remove for Everyone",
"leaveGroupConfirmation": "Are you sure you want to leave this group?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ export const MessageContextMenu = (props: Props) => {
MessageInteraction.unbanUser(sender, convoId);
}, [sender, convoId]);

const onServerBan = useCallback(() => {
MessageInteraction.serverBanUser(sender, convoId);
}, [sender, convoId]);

const onServerUnban = useCallback(() => {
MessageInteraction.serverUnbanUser(sender, convoId);
}, [sender, convoId]);

const onSelect = useCallback(() => {
dispatch(toggleSelectedMessageId(messageId));
}, [messageId]);
Expand Down Expand Up @@ -334,6 +342,10 @@ export const MessageContextMenu = (props: Props) => {
{weAreAdmin && isPublic ? (
<Item onClick={onUnban}>{window.i18n('unbanUser')}</Item>
) : null}
{weAreAdmin && isPublic ? <Item onClick={onServerBan}>{window.i18n('serverBanUser')}</Item> : null}
{weAreAdmin && isPublic ? (
<Item onClick={onServerUnban}>{window.i18n('serverUnbanUser')}</Item>
) : null}
{weAreAdmin && isPublic && !isSenderAdmin ? (
<Item onClick={addModerator}>{window.i18n('addAsModerator')}</Item>
) : null}
Expand Down
141 changes: 133 additions & 8 deletions ts/components/dialog/BanOrUnbanUserDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { Flex } from '../basic/Flex';
import { useDispatch, useSelector } from 'react-redux';
import { BanType, updateBanOrUnbanUserModal } from '../../state/ducks/modalDialog';
import {
BanType,
updateBanOrUnbanUserModal,
updateServerBanOrUnbanUserModal
} from '../../state/ducks/modalDialog';
import { SpacerSM } from '../basic/Text';
import { getConversationController } from '../../session/conversations/ConversationController';
import { SessionWrapperModal } from '../SessionWrapperModal';
Expand All @@ -14,7 +18,9 @@ import { useFocusMount } from '../../hooks/useFocusMount';
import { useConversationPropsById } from '../../hooks/useParamSelector';
import {
sogsV3BanUser,
sogsV3UnbanUser,
sogsV3ServerBanUser,
sogsV3ServerUnbanUser,
sogsV3UnbanUser
} from '../../session/apis/open_group_api/sogsv3/sogsV3BanUnban';
import { SessionHeaderSearchInput } from '../SessionHeaderSearchInput';
import { isDarkTheme } from '../../state/selectors/theme';
Expand All @@ -25,7 +31,8 @@ async function banOrUnBanUserCall(
convo: ConversationModel,
textValue: string,
banType: BanType,
deleteAll: boolean
deleteAll: boolean,
isGlobal: boolean
) {
// if we don't have valid data entered by the user
const pubkey = PubKey.from(textValue);
Expand All @@ -39,13 +46,23 @@ async function banOrUnBanUserCall(
const roomInfos = convo.toOpenGroupV2();
const isChangeApplied =
banType === 'ban'
? await sogsV3BanUser(pubkey, roomInfos, deleteAll)
: await sogsV3UnbanUser(pubkey, roomInfos);
? isGlobal
? await sogsV3ServerBanUser(pubkey, roomInfos, deleteAll)
: await sogsV3BanUser(pubkey, roomInfos, deleteAll)
: isGlobal
? await sogsV3ServerUnbanUser(pubkey, roomInfos)
: await sogsV3UnbanUser(pubkey, roomInfos);

if (!isChangeApplied) {
window?.log?.warn(`failed to ${banType} user: ${isChangeApplied}`);

banType === 'ban' ? ToastUtils.pushUserBanFailure() : ToastUtils.pushUserUnbanSuccess();
banType === 'ban'
? isGlobal
? ToastUtils.pushGlobalUserBanFailure()
: ToastUtils.pushUserBanFailure()
: isGlobal
? ToastUtils.pushGlobalUserUnbanFailure()
: ToastUtils.pushUserUnbanFailure();
return false;
}
window?.log?.info(`${pubkey.key} user ${banType}ned successfully...`);
Expand Down Expand Up @@ -92,7 +109,7 @@ export const BanOrUnBanUserDialog = (props: {

window?.log?.info(`asked to ${banType} user: ${castedPubkey}, banAndDeleteAll:${deleteAll}`);
setInProgress(true);
const isBanned = await banOrUnBanUserCall(convo, castedPubkey, banType, deleteAll);
const isBanned = await banOrUnBanUserCall(convo, castedPubkey, banType, deleteAll, false);
if (isBanned) {
// clear input box
setInputBoxValue('');
Expand Down Expand Up @@ -163,4 +180,112 @@ export const BanOrUnBanUserDialog = (props: {
</Flex>
</SessionWrapperModal>
);
};
}

// FIXME: Refactor with BanOrUnBanUserDialog().
export const ServerBanOrUnBanUserDialog = (props: {
conversationId: string;
banType: BanType;
pubkey?: string;
}) => {
const { conversationId, banType, pubkey } = props;
const { i18n } = window;
const isBan = banType === 'ban';
const dispatch = useDispatch();
const darkMode = useSelector(isDarkTheme);
const convo = getConversationController().get(conversationId);
const inputRef = useRef(null);

useFocusMount(inputRef, true);
const wasGivenAPubkey = Boolean(pubkey?.length);
const [inputBoxValue, setInputBoxValue] = useState('');
const [inProgress, setInProgress] = useState(false);

const sourceConvoProps = useConversationPropsById(pubkey);

const inputTextToDisplay =
wasGivenAPubkey && sourceConvoProps
? `${sourceConvoProps.displayNameInProfile} ${PubKey.shorten(sourceConvoProps.id)}`
: undefined;

/**
* Ban or Unban a user from an open group
* @param deleteAll Delete all messages for that user in the group (only works with ban)
*/
const banOrUnBanUser = async (deleteAll: boolean = false) => {
const castedPubkey = pubkey?.length ? pubkey : inputBoxValue;

window?.log?.info(`asked to ${banType} user server-wide: ${castedPubkey}, banAndDeleteAll:${deleteAll}`);
setInProgress(true);
const isBanned = await banOrUnBanUserCall(convo, castedPubkey, banType, deleteAll, true);
if (isBanned) {
// clear input box
setInputBoxValue('');
if (wasGivenAPubkey) {
dispatch(updateServerBanOrUnbanUserModal(null));
}
}

setInProgress(false);
};

const serverHost = new window.URL(convo.toOpenGroupV2().serverUrl).host;
const title = `${isBan ? window.i18n('banUser') : window.i18n('unbanUser')} @ ${serverHost}`;

const onPubkeyBoxChanges = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputBoxValue(e.target.value?.trim() || '');
};

/**
* Starts procedure for banning/unbanning user and all their messages using dialog
*/
const startBanAndDeleteAllSequence = async () => {
await banOrUnBanUser(true);
};

const buttonText = isBan ? i18n('banUser') : i18n('unbanUser');

return (
<SessionWrapperModal
showExitIcon={true}
title={title}
onClose={() => {
dispatch(updateServerBanOrUnbanUserModal(null));
}}
>
<Flex container={true} flexDirection="column" alignItems="center">
<SessionHeaderSearchInput
ref={inputRef}
type="text"
darkMode={darkMode}
placeholder={i18n('enterSessionID')}
dir="auto"
onChange={onPubkeyBoxChanges}
disabled={inProgress || wasGivenAPubkey}
value={wasGivenAPubkey ? inputTextToDisplay : inputBoxValue}
/>
<Flex container={true}>
<SessionButton
buttonType={SessionButtonType.Simple}
onClick={banOrUnBanUser}
text={buttonText}
disabled={inProgress}
/>
{isBan && (
<>
<SpacerSM />
<SessionButton
buttonType={SessionButtonType.Simple}
buttonColor={SessionButtonColor.Danger}
onClick={startBanAndDeleteAllSequence}
text={i18n('serverBanUserAndDeleteAll')}
disabled={inProgress}
/>
</>
)}
</Flex>
<SessionSpinner loading={inProgress} />
</Flex>
</SessionWrapperModal>
);
}
5 changes: 4 additions & 1 deletion ts/components/dialog/ModalContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getReactListDialog,
getRecoveryPhraseDialog,
getRemoveModeratorsModal,
getServerBanOrUnbanUserModalState,
getSessionPasswordDialog,
getUpdateGroupMembersModal,
getUpdateGroupNameModal,
Expand All @@ -33,7 +34,7 @@ import { RemoveModeratorsDialog } from './ModeratorsRemoveDialog';
import { UpdateGroupMembersDialog } from './UpdateGroupMembersDialog';
import { UpdateGroupNameDialog } from './UpdateGroupNameDialog';
import { SessionNicknameDialog } from './SessionNicknameDialog';
import { BanOrUnBanUserDialog } from './BanOrUnbanUserDialog';
import { BanOrUnBanUserDialog, ServerBanOrUnBanUserDialog } from './BanOrUnbanUserDialog';
import { ReactListModal } from './ReactListModal';
import { ReactClearAllModal } from './ReactClearAllModal';

Expand All @@ -53,12 +54,14 @@ export const ModalContainer = () => {
const sessionPasswordModalState = useSelector(getSessionPasswordDialog);
const deleteAccountModalState = useSelector(getDeleteAccountModalState);
const banOrUnbanUserModalState = useSelector(getBanOrUnbanUserModalState);
const serverBanOrUnbanUserModalState = useSelector(getServerBanOrUnbanUserModalState);
const reactListModalState = useSelector(getReactListDialog);
const reactClearAllModalState = useSelector(getReactClearAllDialog);

return (
<>
{banOrUnbanUserModalState && <BanOrUnBanUserDialog {...banOrUnbanUserModalState} />}
{serverBanOrUnbanUserModalState && <ServerBanOrUnBanUserDialog {...serverBanOrUnbanUserModalState} />}
{inviteModalState && <InviteContactsDialog {...inviteModalState} />}
{addModeratorsModalState && <AddModeratorsDialog {...addModeratorsModalState} />}
{removeModeratorsModalState && <RemoveModeratorsDialog {...removeModeratorsModalState} />}
Expand Down
4 changes: 4 additions & 0 deletions ts/components/menu/ConversationHeaderMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
NotificationForConvoMenuItem,
PinConversationMenuItem,
RemoveModeratorsMenuItem,
ServerBanMenuItem,
ServerUnbanMenuItem,
ShowUserDetailsMenuItem,
UnbanMenuItem,
UpdateGroupNameMenuItem,
Expand Down Expand Up @@ -60,6 +62,8 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
<RemoveModeratorsMenuItem />
<BanMenuItem />
<UnbanMenuItem />
<ServerBanMenuItem />
<ServerUnbanMenuItem />
<UpdateGroupNameMenuItem />
<LeaveGroupMenuItem />
<InviteContactMenuItem />
Expand Down
4 changes: 4 additions & 0 deletions ts/components/menu/ConversationListItemContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
MarkAllReadMenuItem,
NotificationForConvoMenuItem,
PinConversationMenuItem,
ServerBanMenuItem,
ServerUnbanMenuItem,
ShowUserDetailsMenuItem,
UnbanMenuItem,
} from './Menu';
Expand Down Expand Up @@ -44,6 +46,8 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
<DeleteMessagesMenuItem />
<BanMenuItem />
<UnbanMenuItem />
<ServerBanMenuItem />
<ServerUnbanMenuItem />
<InviteContactMenuItem />
<DeleteContactMenuItem />
<LeaveGroupMenuItem />
Expand Down
48 changes: 48 additions & 0 deletions ts/components/menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import {
showInviteContactByConvoId,
showLeaveGroupByConvoId,
showRemoveModeratorsByConvoId,
showServerBanUserByConvoId,
showServerUnbanUserByConvoId,
showUnbanUserByConvoId,
showUpdateGroupNameByConvoId,
unblockConvoById,
Expand Down Expand Up @@ -116,6 +118,14 @@ const showBanUser = (weAreAdmin: boolean, isPublic: boolean, isKickedFromGroup:
return !isKickedFromGroup && weAreAdmin && isPublic;
};

const showServerUnbanUser = (weAreAdmin: boolean, isPublic: boolean) => {
return weAreAdmin && isPublic;
};

const showServerBanUser = (weAreAdmin: boolean, isPublic: boolean) => {
return weAreAdmin && isPublic;
};

function showAddModerators(
weAreAdmin: boolean,
isPublic: boolean,
Expand Down Expand Up @@ -387,6 +397,44 @@ export const BanMenuItem = (): JSX.Element | null => {
return null;
};

export const ServerUnbanMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId);
const isPublic = useIsPublic(convoId);
const weAreAdmin = useWeAreAdmin(convoId);

if (showServerUnbanUser(weAreAdmin, isPublic)) {
return (
<Item
onClick={() => {
showServerUnbanUserByConvoId(convoId);
}}
>
{window.i18n('serverUnbanUser')}
</Item>
);
}
return null;
};

export const ServerBanMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId);
const isPublic = useIsPublic(convoId);
const weAreAdmin = useWeAreAdmin(convoId);

if (showServerBanUser(weAreAdmin, isPublic)) {
return (
<Item
onClick={() => {
showServerBanUserByConvoId(convoId);
}}
>
{window.i18n('serverBanUser')}
</Item>
);
}
return null;
};

export const CopyMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId);
const isPublic = useIsPublic(convoId);
Expand Down
13 changes: 13 additions & 0 deletions ts/interactions/conversationInteractions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
updateGroupNameModal,
updateInviteContactModal,
updateRemoveModeratorsModal,
updateServerBanOrUnbanUserModal
} from '../state/ducks/modalDialog';
import { Data, hasLinkPreviewPopupBeenDisplayed, lastAvatarUploadTimestamp } from '../data/data';
import { quoteMessage, resetConversationExternal } from '../state/ducks/conversations';
Expand Down Expand Up @@ -275,6 +276,18 @@ export function showUnbanUserByConvoId(conversationId: string, pubkey?: string)
);
}

export function showServerBanUserByConvoId(conversationId: string, pubkey?: string) {
window.inboxStore?.dispatch(
updateServerBanOrUnbanUserModal({ banType: 'ban', conversationId, pubkey })
);
}

export function showServerUnbanUserByConvoId(conversationId: string, pubkey?: string) {
window.inboxStore?.dispatch(
updateServerBanOrUnbanUserModal({ banType: 'unban', conversationId, pubkey })
);
}

export async function markAllReadByConvoId(conversationId: string) {
const conversation = getConversationController().get(conversationId);
perfStart(`markAllReadByConvoId-${conversationId}`);
Expand Down
Loading