fix(admin): P0 integrity — permanent bans, ACL sidebar, rank guards
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 1m36s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-22 19:01:59 +02:00
1 parent 11004626c7
commit 75cdfdebe4
14 files changed
+458 -68

No files matched your search

+231 -28
View File
@@ -39,6 +39,7 @@ import {
Wifi,
Wrench,
} from "lucide-react";
import { PERMS } from "@/lib/permission-slugs";
export interface AdminHubTab {
href: string;
@@ -64,6 +65,11 @@ export interface AdminNavItem {
href: string;
labelKey: string;
icon: LucideIcon;
/**
* ACL slug(s) required to show this item. Any match is enough.
* Omit only for the dashboard (already gated by admin.dashboard).
*/
permission?: string | readonly string[];
/** When set, sidebar item is active if pathname matches any prefix. */
matchPrefixes?: string[];
/** Prefixes that must NOT count as active (e.g. /admin/users vs multi-accounts). */
@@ -232,6 +238,7 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin",
labelKey: "dashboard",
icon: LayoutDashboard,
permission: PERMS.ADMIN_DASHBOARD,
},
],
},
@@ -243,31 +250,65 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/articles",
labelKey: "articles",
icon: Newspaper,
permission: PERMS.NEWS_VIEW,
matchPrefixes: ["/admin/articles"],
},
{ href: "/admin/photos", labelKey: "photos", icon: Image },
{ href: "/admin/banners", labelKey: "banners", icon: Megaphone },
{
href: "/admin/photos",
labelKey: "photos",
icon: Image,
permission: PERMS.PAGES_VIEW,
},
{
href: "/admin/banners",
labelKey: "banners",
icon: Megaphone,
permission: PERMS.BANNERS_VIEW,
},
{
href: "/admin/ads",
labelKey: "advertisements",
icon: FileText,
permission: PERMS.PAGES_VIEW,
matchPrefixes: ["/admin/ads"],
},
{ href: "/admin/media", labelKey: "media", icon: Image },
{ href: "/admin/navigation", labelKey: "navigator", icon: Compass },
{
href: "/admin/media",
labelKey: "media",
icon: Image,
permission: PERMS.PAGES_VIEW,
},
{
href: "/admin/navigation",
labelKey: "navigator",
icon: Compass,
permission: PERMS.PAGES_VIEW,
},
{
href: "/admin/help-questions",
labelKey: "helpCenter",
icon: HelpCircle,
permission: PERMS.PAGES_VIEW,
matchPrefixes: ["/admin/help-questions"],
},
{
href: "/admin/writeable-boxes",
labelKey: "writeableBoxes",
icon: Package,
permission: PERMS.PAGES_VIEW,
},
{
href: "/admin/tags",
labelKey: "tags",
icon: Tags,
permission: PERMS.PAGES_VIEW,
},
{
href: "/admin/prefixes",
labelKey: "prefixes",
icon: Sparkles,
permission: PERMS.PREFIXES_VIEW,
},
{ href: "/admin/tags", labelKey: "tags", icon: Tags },
{ href: "/admin/prefixes", labelKey: "prefixes", icon: Sparkles },
],
},
{
@@ -278,18 +319,21 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/events",
labelKey: "events",
icon: Calendar,
permission: PERMS.EVENTS_VIEW,
matchPrefixes: ["/admin/events"],
},
{
href: "/admin/polls",
labelKey: "polls",
icon: Vote,
permission: PERMS.POLLS_VIEW,
matchPrefixes: ["/admin/polls"],
},
{
href: "/admin/tickets",
labelKey: "tickets",
icon: Ticket,
permission: PERMS.TICKETS_VIEW,
matchPrefixes: ["/admin/tickets", "/admin/help-tickets"],
},
],
@@ -302,6 +346,7 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/users",
labelKey: "users",
icon: Users,
permission: PERMS.USERS_VIEW,
matchPrefixes: ["/admin/users"],
matchExcludePrefixes: ["/admin/users/multi-accounts"],
},
@@ -309,31 +354,64 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/users/multi-accounts",
labelKey: "multiAccounts",
icon: Users,
permission: PERMS.USERS_VIEW,
},
{
href: "/admin/online",
labelKey: "onlineUsers",
icon: Wifi,
permission: PERMS.USERS_VIEW,
},
{ href: "/admin/online", labelKey: "onlineUsers", icon: Wifi },
{
href: "/admin/applications",
labelKey: "applications",
icon: ClipboardList,
permission: PERMS.USERS_VIEW,
},
{
href: "/admin/teams",
labelKey: "staffAccess",
icon: KeyRound,
permission: [
PERMS.USERS_VIEW,
PERMS.PERMISSIONS_MANAGE,
PERMS.SETTINGS_VIEW,
],
matchPrefixes: [
"/admin/teams",
"/admin/permissions",
"/admin/housekeeping",
],
},
{ href: "/admin/bans", labelKey: "bans", icon: Ban },
{ href: "/admin/ip", labelKey: "ipManagement", icon: Shield },
{ href: "/admin/vpn", labelKey: "vpn", icon: Shield },
{ href: "/admin/wordfilter", labelKey: "wordFilter", icon: Filter },
{
href: "/admin/bans",
labelKey: "bans",
icon: Ban,
permission: PERMS.BANS_VIEW,
},
{
href: "/admin/ip",
labelKey: "ipManagement",
icon: Shield,
permission: PERMS.SETTINGS_VIEW,
},
{
href: "/admin/vpn",
labelKey: "vpn",
icon: Shield,
permission: PERMS.SETTINGS_VIEW,
},
{
href: "/admin/wordfilter",
labelKey: "wordFilter",
icon: Filter,
permission: PERMS.WORDFILTER_VIEW,
},
{
href: "/admin/moderation",
labelKey: "moderation",
icon: Gavel,
permission: PERMS.MODERATION_VIEW,
matchPrefixes: ["/admin/moderation"],
},
],
@@ -346,24 +424,62 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/catalog",
labelKey: "catalog",
icon: Store,
permission: PERMS.CATALOG_VIEW,
matchPrefixes: ["/admin/catalog"],
},
{ href: "/admin/rare-values", labelKey: "rareValues", icon: Sparkles },
{ href: "/admin/badges", labelKey: "badges", icon: BadgeCheck },
{ href: "/admin/achievements", labelKey: "achievements", icon: Trophy },
{ href: "/admin/sounds", labelKey: "sounds", icon: Volume2 },
{ href: "/admin/shop", labelKey: "shop", icon: ShoppingCart },
{ href: "/admin/transactions", labelKey: "transactions", icon: Activity },
{ href: "/admin/vouchers", labelKey: "vouchers", icon: Ticket },
{
href: "/admin/rare-values",
labelKey: "rareValues",
icon: Sparkles,
permission: PERMS.SHOP_VIEW,
},
{
href: "/admin/badges",
labelKey: "badges",
icon: BadgeCheck,
permission: PERMS.CATALOG_VIEW,
},
{
href: "/admin/achievements",
labelKey: "achievements",
icon: Trophy,
permission: PERMS.CATALOG_VIEW,
},
{
href: "/admin/sounds",
labelKey: "sounds",
icon: Volume2,
permission: PERMS.CATALOG_VIEW,
},
{
href: "/admin/shop",
labelKey: "shop",
icon: ShoppingCart,
permission: PERMS.SHOP_VIEW,
},
{
href: "/admin/transactions",
labelKey: "transactions",
icon: Activity,
permission: PERMS.SHOP_VIEW,
},
{
href: "/admin/vouchers",
labelKey: "vouchers",
icon: Ticket,
permission: PERMS.SHOP_VIEW,
},
{
href: "/admin/subscriptions",
labelKey: "subscriptions",
icon: ClipboardList,
permission: PERMS.SHOP_VIEW,
},
{
href: "/admin/calendar",
labelKey: "calendar",
icon: CalendarDays,
permission: PERMS.SHOP_VIEW,
matchPrefixes: ["/admin/calendar"],
},
],
@@ -376,6 +492,7 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/radio",
labelKey: "radio",
icon: Radio,
permission: PERMS.RADIO_VIEW,
matchPrefixes: ["/admin/radio"],
},
],
@@ -384,15 +501,41 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
labelKey: "system",
icon: Settings,
items: [
{ href: "/admin/settings", labelKey: "settings", icon: Cog },
{ href: "/admin/theme", labelKey: "theme", icon: Sparkles },
{ href: "/admin/maintenance", labelKey: "maintenance", icon: Wrench },
{ href: "/admin/favicon", labelKey: "favicon", icon: Image },
{ href: "/admin/emulator", labelKey: "emulator", icon: Server },
{
href: "/admin/settings",
labelKey: "settings",
icon: Cog,
permission: PERMS.SETTINGS_VIEW,
},
{
href: "/admin/theme",
labelKey: "theme",
icon: Sparkles,
permission: PERMS.SETTINGS_VIEW,
},
{
href: "/admin/maintenance",
labelKey: "maintenance",
icon: Wrench,
permission: PERMS.SETTINGS_VIEW,
},
{
href: "/admin/favicon",
labelKey: "favicon",
icon: Image,
permission: PERMS.SETTINGS_VIEW,
},
{
href: "/admin/emulator",
labelKey: "emulator",
icon: Server,
permission: PERMS.SETTINGS_VIEW,
},
{
href: "/admin/email-templates",
labelKey: "emailTemplates",
icon: FileText,
permission: PERMS.PAGES_VIEW,
},
],
},
@@ -404,23 +547,27 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/commandocentrum",
labelKey: "commandocentrum",
icon: Terminal,
permission: PERMS.RCON_EXECUTE,
},
{
href: "/admin/rooms",
labelKey: "rooms",
icon: Store,
permission: PERMS.ROOMS_VIEW,
matchPrefixes: ["/admin/rooms"],
},
{
href: "/admin/import",
labelKey: "import",
icon: Import,
permission: PERMS.ASSETS_IMPORT,
matchPrefixes: ["/admin/import"],
},
{
href: "/admin/translations",
labelKey: "translations",
icon: Languages,
permission: PERMS.SETTINGS_VIEW,
matchPrefixes: ["/admin/translations"],
},
],
@@ -433,6 +580,7 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
href: "/admin/logs",
labelKey: "logs",
icon: FileText,
permission: PERMS.LOGS_VIEW,
matchPrefixes: ["/admin/logs"],
matchExcludePrefixes: [
"/admin/logs/audit",
@@ -441,23 +589,78 @@ export const ADMIN_NAV_GROUPS: AdminNavGroup[] = [
"/admin/logs/trades",
],
},
{ href: "/admin/logs/audit", labelKey: "auditLog", icon: ClipboardList },
{ href: "/admin/logs/chat", labelKey: "chatLog", icon: FileText },
{ href: "/admin/logs/commands", labelKey: "commandLog", icon: Terminal },
{ href: "/admin/logs/trades", labelKey: "tradeLog", icon: Activity },
{
href: "/admin/logs/audit",
labelKey: "auditLog",
icon: ClipboardList,
permission: PERMS.LOGS_VIEW,
},
{
href: "/admin/logs/chat",
labelKey: "chatLog",
icon: FileText,
permission: PERMS.LOGS_VIEW,
},
{
href: "/admin/logs/commands",
labelKey: "commandLog",
icon: Terminal,
permission: PERMS.LOGS_VIEW,
},
{
href: "/admin/logs/trades",
labelKey: "tradeLog",
icon: Activity,
permission: PERMS.LOGS_VIEW,
},
{
href: "/admin/analytics",
labelKey: "analytics",
icon: Activity,
permission: PERMS.ANALYTICS_VIEW,
matchPrefixes: ["/admin/analytics"],
},
{
href: "/admin/devops",
labelKey: "devops",
icon: Server,
permission: PERMS.DEVOPS_VIEW,
matchPrefixes: ["/admin/devops"],
},
{ href: "/admin/alerts", labelKey: "alerts", icon: AlertTriangle },
{
href: "/admin/alerts",
labelKey: "alerts",
icon: AlertTriangle,
permission: PERMS.NOTIFICATIONS_VIEW,
},
],
},
];
/** Whether a nav item should be visible for the given permission set. */
export function navItemIsAllowed(
item: AdminNavItem,
opts: { isSuperAdmin: boolean; has: (slug: string) => boolean },
): boolean {
if (opts.isSuperAdmin) return true;
if (!item.permission) return true;
const needed = Array.isArray(item.permission)
? item.permission
: [item.permission];
return needed.some((slug) => opts.has(slug));
}
/** Collect every ACL slug referenced by the sidebar (for layout gating). */
export function collectNavPermissionSlugs(): string[] {
const slugs = new Set<string>();
for (const group of ADMIN_NAV_GROUPS) {
for (const item of group.items) {
if (!item.permission) continue;
const needed = Array.isArray(item.permission)
? item.permission
: [item.permission];
for (const slug of needed) slugs.add(slug);
}
}
return [...slugs];
}