From bc5d832e0d3faf5dc6f64a6359e32d75e68c4282 Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 25 Feb 2026 04:29:35 +0300 Subject: [PATCH] fix: guard user detail API calls with RBAC permission checks Hide sync tab and skip sync/promo-groups API calls when moderator lacks users:sync and promo_groups:read permissions. --- src/pages/AdminUserDetail.tsx | 61 +++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/pages/AdminUserDetail.tsx b/src/pages/AdminUserDetail.tsx index 74a5f20..73b9db3 100644 --- a/src/pages/AdminUserDetail.tsx +++ b/src/pages/AdminUserDetail.tsx @@ -20,6 +20,7 @@ import { promoOffersApi } from '../api/promoOffers'; import { ticketsApi } from '../api/tickets'; import { AdminBackButton } from '../components/admin'; import { createNumberInputHandler, toNumber } from '../utils/inputHelpers'; +import { usePermissionStore } from '../store/permissions'; // ============ Helpers ============ @@ -135,6 +136,7 @@ export default function AdminUserDetail() { const navigate = useNavigate(); const notify = useNotify(); const { id } = useParams<{ id: string }>(); + const hasPermission = usePermissionStore((s) => s.hasPermission); const localeMap: Record = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' }; const locale = localeMap[i18n.language] || 'ru-RU'; @@ -384,9 +386,9 @@ export default function AdminUserDetail() { useEffect(() => { if (activeTab === 'info') { loadReferrals(); - loadPromoGroups(); + if (hasPermission('promo_groups:read')) loadPromoGroups(); } - if (activeTab === 'sync') loadSyncStatus(); + if (activeTab === 'sync' && hasPermission('users:sync')) loadSyncStatus(); if (activeTab === 'subscription') { loadTariffs(); loadSubscriptionData(); @@ -400,6 +402,7 @@ export default function AdminUserDetail() { loadReferrals, loadSubscriptionData, loadPromoGroups, + hasPermission, ]); const handleUpdateBalance = async (isAdd: boolean) => { @@ -831,23 +834,25 @@ export default function AdminUserDetail() { className="scrollbar-hide -mx-4 mb-6 flex gap-2 overflow-x-auto px-4 py-1" style={{ WebkitOverflowScrolling: 'touch' }} > - {(['info', 'subscription', 'balance', 'sync', 'tickets'] as const).map((tab) => ( - - ))} + {(['info', 'subscription', 'balance', 'sync', 'tickets'] as const) + .filter((tab) => tab !== 'sync' || hasPermission('users:sync')) + .map((tab) => ( + + ))} {/* Content */} @@ -930,14 +935,16 @@ export default function AdminUserDetail() {
{t('admin.users.detail.promoGroup')} - + {hasPermission('promo_groups:read') && ( + + )}
{editingPromoGroup ? (