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; postStories?: true;
editStories?: true; editStories?: true;
deleteStories?: true; deleteStories?: true;
manageDirectMessages?: true;
} }
export interface ApiChatBannedRights { export interface ApiChatBannedRights {

View File

@ -955,6 +955,14 @@
"EditAdminBanUsers" = "Ban Users"; "EditAdminBanUsers" = "Ban Users";
"EditAdminPinMessages" = "Pin Messages"; "EditAdminPinMessages" = "Pin Messages";
"EditAdminAddAdmins" = "Add New Admins"; "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"; "StartVoipChatPermission" = "Manage Video Chats";
"EditAdminSendAnonymously" = "Remain Anonymous"; "EditAdminSendAnonymously" = "Remain Anonymous";
"ChannelEditAdminCannotEdit" = "You can't edit the rights of this admin."; "ChannelEditAdminCannotEdit" = "You can't edit the rights of this admin.";

View File

@ -24,7 +24,8 @@ import {
selectBot, selectBot,
selectCanDeleteSelectedMessages, selectCanDeleteSelectedMessages,
selectChat, selectChat,
selectChatFullInfo, selectIsChatWithBot, selectChatFullInfo,
selectIsChatWithBot,
selectSenderFromMessage, selectSenderFromMessage,
selectTabState, selectTabState,
selectUser, selectUser,
@ -164,7 +165,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
const peerListToBan = useMemo(() => { const peerListToBan = useMemo(() => {
const isCurrentUserInList = peerList.some((peer) => peer.id === currentUserId); const isCurrentUserInList = peerList.some((peer) => peer.id === currentUserId);
const shouldReturnEmpty = !canBanUsers || isCurrentUserInList || chat?.isMonoforum; const shouldReturnEmpty = !canBanUsers || isCurrentUserInList;
if (shouldReturnEmpty) { if (shouldReturnEmpty) {
return MEMO_EMPTY_ARRAY; return MEMO_EMPTY_ARRAY;
@ -174,7 +175,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
const isAdmin = adminMembersById?.[peer.id]; const isAdmin = adminMembersById?.[peer.id];
return isCreator || !isAdmin; return isCreator || !isAdmin;
}); });
}, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById, chat?.isMonoforum]); }, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById]);
const shouldShowAdditionalOptions = useMemo(() => { const shouldShowAdditionalOptions = useMemo(() => {
return Boolean(peerListToDeleteAll.length || peerListToReportSpam.length || peerListToBan.length); return Boolean(peerListToDeleteAll.length || peerListToReportSpam.length || peerListToBan.length);
@ -510,7 +511,7 @@ export default memo(withGlobal<OwnProps>(
: undefined; : undefined;
const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id)); const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id));
const adminMembersById = chatFullInfo?.adminMembersById; 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 isCreator = chat?.isCreator;
const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined; const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined;
const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot; 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 { import {
memo, useMemo, memo, useMemo,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
@ -19,15 +17,15 @@ import Checkbox from '../ui/Checkbox';
export type OwnProps = { export type OwnProps = {
chatId?: string; chatId?: string;
handlePermissionChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
permissions: ApiChatBannedRights; permissions: ApiChatBannedRights;
isMediaDropdownOpen: boolean; isMediaDropdownOpen: boolean;
setIsMediaDropdownOpen: (open: boolean) => void;
className?: string; className?: string;
shiftedClassName?: string; shiftedClassName?: string;
dropdownClassName?: string; dropdownClassName?: string;
withCheckbox?: boolean; withCheckbox?: boolean;
permissionGroup?: boolean; permissionGroup?: boolean;
handlePermissionChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
setIsMediaDropdownOpen: (open: boolean) => void;
getControlIsDisabled?: (key: Exclude<keyof ApiChatBannedRights, 'untilDate'>) => boolean | undefined; getControlIsDisabled?: (key: Exclude<keyof ApiChatBannedRights, 'untilDate'>) => boolean | undefined;
}; };
@ -41,20 +39,20 @@ const permissionKeyList: (keyof ApiChatBannedRights)[] = [
'sendAudios', 'sendDocs', 'sendVoices', 'sendRoundvideos', 'embedLinks', 'sendPolls', 'sendAudios', 'sendDocs', 'sendVoices', 'sendRoundvideos', 'embedLinks', 'sendPolls',
]; ];
const PermissionCheckboxList: FC<OwnProps & StateProps> = ({ const PermissionCheckboxList = ({
chat, chat,
isMediaDropdownOpen, isMediaDropdownOpen,
setIsMediaDropdownOpen,
hasLinkedChat, hasLinkedChat,
permissions, permissions,
handlePermissionChange,
className, className,
shiftedClassName, shiftedClassName,
dropdownClassName, dropdownClassName,
withCheckbox, withCheckbox,
getControlIsDisabled,
permissionGroup, permissionGroup,
}) => { setIsMediaDropdownOpen,
handlePermissionChange,
getControlIsDisabled,
}: OwnProps & StateProps) => {
const { const {
showNotification, showNotification,
} = getActions(); } = getActions();

View File

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

View File

@ -17,6 +17,7 @@ import {
deletePeerPhoto, deletePeerPhoto,
leaveChat, leaveChat,
removeUnreadMentions, removeUnreadMentions,
replaceChatMessages,
replacePeerPhotos, replacePeerPhotos,
replacePinnedTopicIds, replacePinnedTopicIds,
replaceThreadParam, replaceThreadParam,
@ -69,6 +70,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
global = updatePeerStoriesHidden(global, update.id, update.chat.areStoriesHidden || false); 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); setGlobal(global);
const updatedChat = selectChat(global, update.id); const updatedChat = selectChat(global, update.id);

View File

@ -96,7 +96,9 @@ export function updateCurrentMessageList<T extends GlobalState>(
}, tabId); }, 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, { return updateMessageStore(global, chatId, {
byId: newById, byId: newById,
}); });

View File

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

View File

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