Monoforum: Fix admin rights check (#6538)

This commit is contained in:
zubiden 2026-01-13 01:14:14 +01:00 committed by Alexander Zinchuk
parent 9b17732097
commit ade72a0727
10 changed files with 73 additions and 41 deletions

View File

@ -197,6 +197,7 @@ export interface ApiChatAdminRights {
postStories?: true;
editStories?: true;
deleteStories?: true;
manageDirectMessages?: true;
}
export interface ApiChatBannedRights {

View File

@ -955,6 +955,14 @@
"EditAdminBanUsers" = "Ban Users";
"EditAdminPinMessages" = "Pin Messages";
"EditAdminAddAdmins" = "Add New Admins";
"EditAdminManageDirect" = "Manage Direct Messages";
"EditAdminPostStories" = "Post Stories";
"EditAdminEditStories" = "Edit Stories of Others";
"EditAdminDeleteStories" = "Delete Stories of Others";
"EditAdminManageTopics" = "Manage Topics";
"EditAdminUnavailable" = "You can't edit the rights of this admin.";
"EditAdminConfirmDismissText" = "Are you sure you want to dismiss this admin?";
"EditAdminConfirmDismiss" = "Dismiss Admin";
"StartVoipChatPermission" = "Manage Video Chats";
"EditAdminSendAnonymously" = "Remain Anonymous";
"ChannelEditAdminCannotEdit" = "You can't edit the rights of this admin.";

View File

@ -24,7 +24,8 @@ import {
selectBot,
selectCanDeleteSelectedMessages,
selectChat,
selectChatFullInfo, selectIsChatWithBot,
selectChatFullInfo,
selectIsChatWithBot,
selectSenderFromMessage,
selectTabState,
selectUser,
@ -164,7 +165,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
const peerListToBan = useMemo(() => {
const isCurrentUserInList = peerList.some((peer) => peer.id === currentUserId);
const shouldReturnEmpty = !canBanUsers || isCurrentUserInList || chat?.isMonoforum;
const shouldReturnEmpty = !canBanUsers || isCurrentUserInList;
if (shouldReturnEmpty) {
return MEMO_EMPTY_ARRAY;
@ -174,7 +175,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
const isAdmin = adminMembersById?.[peer.id];
return isCreator || !isAdmin;
});
}, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById, chat?.isMonoforum]);
}, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById]);
const shouldShowAdditionalOptions = useMemo(() => {
return Boolean(peerListToDeleteAll.length || peerListToReportSpam.length || peerListToBan.length);
@ -510,7 +511,7 @@ export default memo(withGlobal<OwnProps>(
: undefined;
const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id));
const adminMembersById = chatFullInfo?.adminMembersById;
const canBanUsers = chat && getHasAdminRight(chat, 'banUsers');
const canBanUsers = chat && getHasAdminRight(chat, 'banUsers') && !chat.isMonoforum; // TODO: Ban in channel in case of monoforum
const isCreator = chat?.isCreator;
const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined;
const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot;

View File

@ -1,5 +1,3 @@
import type { FC } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import {
memo, useMemo,
} from '../../lib/teact/teact';
@ -19,15 +17,15 @@ import Checkbox from '../ui/Checkbox';
export type OwnProps = {
chatId?: string;
handlePermissionChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
permissions: ApiChatBannedRights;
isMediaDropdownOpen: boolean;
setIsMediaDropdownOpen: (open: boolean) => void;
className?: string;
shiftedClassName?: string;
dropdownClassName?: string;
withCheckbox?: boolean;
permissionGroup?: boolean;
handlePermissionChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
setIsMediaDropdownOpen: (open: boolean) => void;
getControlIsDisabled?: (key: Exclude<keyof ApiChatBannedRights, 'untilDate'>) => boolean | undefined;
};
@ -41,20 +39,20 @@ const permissionKeyList: (keyof ApiChatBannedRights)[] = [
'sendAudios', 'sendDocs', 'sendVoices', 'sendRoundvideos', 'embedLinks', 'sendPolls',
];
const PermissionCheckboxList: FC<OwnProps & StateProps> = ({
const PermissionCheckboxList = ({
chat,
isMediaDropdownOpen,
setIsMediaDropdownOpen,
hasLinkedChat,
permissions,
handlePermissionChange,
className,
shiftedClassName,
dropdownClassName,
withCheckbox,
getControlIsDisabled,
permissionGroup,
}) => {
setIsMediaDropdownOpen,
handlePermissionChange,
getControlIsDisabled,
}: OwnProps & StateProps) => {
const {
showNotification,
} = getActions();

View File

@ -1,5 +1,3 @@
import type { FC } from '../../../lib/teact/teact';
import type React from '../../../lib/teact/teact';
import {
memo, useCallback, useEffect, useMemo, useState,
} from '../../../lib/teact/teact';
@ -15,7 +13,7 @@ import { selectChat, selectChatFullInfo } from '../../../global/selectors';
import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang';
import useLang from '../../../hooks/useLang';
import PrivateChatInfo from '../../common/PrivateChatInfo';
import Checkbox from '../../ui/Checkbox';
@ -40,15 +38,13 @@ type StateProps = {
adminMembersById?: Record<string, ApiChatMember>;
hasFullInfo: boolean;
currentUserId?: string;
isChannel: boolean;
isFormFullyDisabled: boolean;
isForum?: boolean;
defaultRights?: ApiChatAdminRights;
};
const CUSTOM_TITLE_MAX_LENGTH = 16;
const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
const ManageGroupAdminRights = ({
isActive,
isNewAdmin,
selectedUserId,
@ -58,12 +54,10 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
currentUserId,
adminMembersById,
hasFullInfo,
isChannel,
isForum,
isFormFullyDisabled,
onClose,
onScreenSelect,
}) => {
}: OwnProps & StateProps) => {
const { updateChatAdmin } = getActions();
const [permissions, setPermissions] = useState<ApiChatAdminRights>({});
@ -71,7 +65,11 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
const [isLoading, setIsLoading] = useState(false);
const [isDismissConfirmationDialogOpen, openDismissConfirmationDialog, closeDismissConfirmationDialog] = useFlag();
const [customTitle, setCustomTitle] = useState('');
const lang = useOldLang();
const lang = useLang();
const isChannel = isChatChannel(chat);
const isForum = chat.isForum;
const hasDirectMessages = Boolean(chat.linkedMonoforumId);
useHistoryBack({
isActive,
@ -186,7 +184,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
: undefined;
if (promotedByUser) {
return lang('EditAdminPromotedBy', getUserFullName(promotedByUser));
return lang('EditAdminPromotedBy', { user: getUserFullName(promotedByUser) });
}
return lang('ChannelAdmin');
@ -290,18 +288,28 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
onChange={handlePermissionChange}
/>
</div>
{!isChannel && (
{hasDirectMessages && (
<div className="ListItem">
<Checkbox
name="banUsers"
checked={Boolean(permissions.banUsers)}
label={lang('EditAdminBanUsers')}
name="manageDirectMessages"
checked={Boolean(permissions.manageDirectMessages)}
label={lang('EditAdminManageDirect')}
blocking
disabled={getControlIsDisabled('banUsers')}
disabled={getControlIsDisabled('manageDirectMessages')}
onChange={handlePermissionChange}
/>
</div>
)}
<div className="ListItem">
<Checkbox
name="banUsers"
checked={Boolean(permissions.banUsers)}
label={lang('EditAdminBanUsers')}
blocking
disabled={getControlIsDisabled('banUsers')}
onChange={handlePermissionChange}
/>
</div>
<div className="ListItem">
<Checkbox
name="inviteUsers"
@ -349,7 +357,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
<Checkbox
name="manageTopics"
checked={Boolean(permissions.manageTopics)}
label={lang('ManageTopicsPermission')}
label={lang('EditAdminManageTopics')}
blocking
disabled={getControlIsDisabled('manageTopics')}
onChange={handlePermissionChange}
@ -371,7 +379,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
{isFormFullyDisabled && (
<p className="section-info mb-4" dir="auto">
{lang('Channel.EditAdmin.CannotEdit')}
{lang('EditAdminUnavailable')}
</p>
)}
@ -407,8 +415,8 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
<ConfirmDialog
isOpen={isDismissConfirmationDialogOpen}
onClose={closeDismissConfirmationDialog}
text="Are you sure you want to dismiss this admin?"
confirmLabel={lang('Channel.Admin.Dismiss')}
text={lang('EditAdminConfirmDismissText')}
confirmLabel={lang('EditAdminConfirmDismiss')}
confirmHandler={handleDismissAdmin}
confirmIsDestructive
/>
@ -423,16 +431,12 @@ export default memo(withGlobal<OwnProps>(
const fullInfo = selectChatFullInfo(global, chatId);
const { byId: usersById } = global.users;
const { currentUserId } = global;
const isChannel = isChatChannel(chat);
const isFormFullyDisabled = !(chat.isCreator || isPromotedByCurrentUser);
const isForum = chat.isForum;
return {
chat,
usersById,
currentUserId,
isChannel,
isForum,
isFormFullyDisabled,
defaultRights: chat.adminRights,
hasFullInfo: Boolean(fullInfo),

View File

@ -17,6 +17,7 @@ import {
deletePeerPhoto,
leaveChat,
removeUnreadMentions,
replaceChatMessages,
replacePeerPhotos,
replacePinnedTopicIds,
replaceThreadParam,
@ -69,6 +70,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
global = updatePeerStoriesHidden(global, update.id, update.chat.areStoriesHidden || false);
}
const localAdminRights = localChat?.adminRights;
const newAdminRights = update.chat.adminRights;
if (localAdminRights && localAdminRights.manageDirectMessages && !update.chat.isMin
&& newAdminRights?.manageDirectMessages !== localAdminRights.manageDirectMessages
&& localChat.linkedMonoforumId) {
global = replaceChatMessages(global, localChat.linkedMonoforumId, {});
}
setGlobal(global);
const updatedChat = selectChat(global, update.id);

View File

@ -96,7 +96,9 @@ export function updateCurrentMessageList<T extends GlobalState>(
}, tabId);
}
function replaceChatMessages<T extends GlobalState>(global: T, chatId: string, newById: Record<number, ApiMessage>): T {
export function replaceChatMessages<T extends GlobalState>(
global: T, chatId: string, newById: Record<number, ApiMessage>,
): T {
return updateMessageStore(global, chatId, {
byId: newById,
});

View File

@ -360,7 +360,7 @@ export function selectIsMonoforumAdmin<T extends GlobalState>(
const channel = selectMonoforumChannel(global, chatId);
if (!channel) return;
return Boolean(chat.isCreator || chat.adminRights || channel.isCreator || channel.adminRights);
return Boolean(chat.isCreator || getHasAdminRight(channel, 'manageDirectMessages'));
}
/**

View File

@ -5,7 +5,7 @@ import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
import { isUserId } from '../../util/entities/ids';
import { getCurrentTabId } from '../../util/establishMultitabRole';
import { getHasAdminRight, isChatAdmin, isChatChannel, isDeletedUser } from '../helpers';
import { selectChat, selectChatFullInfo } from './chats';
import { selectChat, selectChatFullInfo, selectIsMonoforumAdmin } from './chats';
import { type ProfileCollectionKey } from './payments';
import { selectTabState } from './tabs';
import { selectBot, selectUser, selectUserFullInfo } from './users';
@ -66,7 +66,7 @@ export function selectPeerPaidMessagesStars<T extends GlobalState>(
const chat = selectChat(global, peerId);
if (!chat) return undefined;
if (isChatAdmin(chat)) return undefined;
if (isChatAdmin(chat) || selectIsMonoforumAdmin(global, chat.id)) return undefined;
return chat.paidMessagesStars;
}

View File

@ -831,6 +831,14 @@ export interface LangPair {
'EditAdminBanUsers': undefined;
'EditAdminPinMessages': undefined;
'EditAdminAddAdmins': undefined;
'EditAdminManageDirect': undefined;
'EditAdminPostStories': undefined;
'EditAdminEditStories': undefined;
'EditAdminDeleteStories': undefined;
'EditAdminManageTopics': undefined;
'EditAdminUnavailable': undefined;
'EditAdminConfirmDismissText': undefined;
'EditAdminConfirmDismiss': undefined;
'StartVoipChatPermission': undefined;
'EditAdminSendAnonymously': undefined;
'ChannelEditAdminCannotEdit': undefined;