fix(admin): restore sidebar categories via ACL grant repair
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
ce6e8235cc
commit
255c09b9fd
3 files changed
+94
-4
No files matched your search
@@ -0,0 +1,69 @@
|
|||||||
|
-- Repair admin ACL so ranks that can open the panel also see/use admin sections.
|
||||||
|
-- Root cause: sidebar filters by ACL; seed only granted some admin.*.view rows,
|
||||||
|
-- and later sync from housekeeping could leave mid/high ranks incomplete.
|
||||||
|
|
||||||
|
-- 1) Every rank that already has admin.dashboard gets ALL admin.* permissions.
|
||||||
|
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
|
||||||
|
SELECT 'Role', ar.id, ap.id
|
||||||
|
FROM `acl_roles` ar
|
||||||
|
JOIN `acl_permissions` ap ON ap.slug LIKE 'admin.%'
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `acl_model_permissions` amp
|
||||||
|
JOIN `acl_permissions` apdash ON apdash.id = amp.permission_id
|
||||||
|
WHERE amp.model_type = 'Role'
|
||||||
|
AND amp.model_id = ar.id
|
||||||
|
AND apdash.slug = 'admin.dashboard'
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `acl_model_permissions` amp2
|
||||||
|
WHERE amp2.model_type = 'Role'
|
||||||
|
AND amp2.model_id = ar.id
|
||||||
|
AND amp2.permission_id = ap.id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 2) Safety net matching original seed: ranks >= 6 get all admin.*.view + dashboard.
|
||||||
|
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
|
||||||
|
SELECT 'Role', ar.id, ap.id
|
||||||
|
FROM `permission_ranks` pr
|
||||||
|
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
|
||||||
|
JOIN `acl_permissions` ap ON (
|
||||||
|
ap.slug = 'admin.dashboard'
|
||||||
|
OR (ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.view')
|
||||||
|
)
|
||||||
|
WHERE pr.id >= 6
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `acl_model_permissions` amp
|
||||||
|
WHERE amp.model_type = 'Role'
|
||||||
|
AND amp.model_id = ar.id
|
||||||
|
AND amp.permission_id = ap.id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3) Ranks >= 7 also get edit/manage/execute tools used by the sidebar.
|
||||||
|
INSERT INTO `acl_model_permissions` (`model_type`, `model_id`, `permission_id`)
|
||||||
|
SELECT 'Role', ar.id, ap.id
|
||||||
|
FROM `permission_ranks` pr
|
||||||
|
JOIN `acl_roles` ar ON ar.slug = CONCAT('rank_', pr.id)
|
||||||
|
JOIN `acl_permissions` ap ON (
|
||||||
|
(ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.edit')
|
||||||
|
OR ap.slug IN (
|
||||||
|
'admin.permissions.manage',
|
||||||
|
'admin.rcon.execute',
|
||||||
|
'admin.assets.import',
|
||||||
|
'admin.export',
|
||||||
|
'admin.analytics.export',
|
||||||
|
'admin.users.ban',
|
||||||
|
'admin.users.reset_password',
|
||||||
|
'admin.room.delete'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHERE pr.id >= 7
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `acl_model_permissions` amp
|
||||||
|
WHERE amp.model_type = 'Role'
|
||||||
|
AND amp.model_id = ar.id
|
||||||
|
AND amp.permission_id = ap.id
|
||||||
|
);
|
||||||
@@ -12,7 +12,7 @@ import { ThemeSwitcher } from "@/components/theme-switcher";
|
|||||||
import { requireStaff } from "@/lib/admin/guard";
|
import { requireStaff } from "@/lib/admin/guard";
|
||||||
import { collectNavPermissionSlugs } from "@/lib/admin-nav";
|
import { collectNavPermissionSlugs } from "@/lib/admin-nav";
|
||||||
import { setCsrfCookie } from "@/lib/foundation/security";
|
import { setCsrfCookie } from "@/lib/foundation/security";
|
||||||
import { canAccess, getAdminContext } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
|
|
||||||
@@ -70,11 +70,21 @@ async function Sidebar({
|
|||||||
const initial = staff.username.charAt(0).toUpperCase();
|
const initial = staff.username.charAt(0).toUpperCase();
|
||||||
const { permissions } = await getAdminContext();
|
const { permissions } = await getAdminContext();
|
||||||
const isSuperAdmin = permissions.isSuperAdmin;
|
const isSuperAdmin = permissions.isSuperAdmin;
|
||||||
|
const hasDashboard = canAccess(
|
||||||
|
permissions,
|
||||||
|
PERMS.ADMIN_DASHBOARD,
|
||||||
|
staff.rank,
|
||||||
|
);
|
||||||
|
// Super-admins see everything. Everyone else sees items their ACL grants.
|
||||||
|
// If they can open the admin panel but ACL is incomplete (common after HK→ACL
|
||||||
|
// migration gaps), still show *.view nav entries so categories do not vanish.
|
||||||
const allowedPermissions = isSuperAdmin
|
const allowedPermissions = isSuperAdmin
|
||||||
? []
|
? []
|
||||||
: collectNavPermissionSlugs().filter((slug) =>
|
: collectNavPermissionSlugs().filter((slug) => {
|
||||||
canAccess(permissions, slug, staff.rank),
|
if (canAccess(permissions, slug, staff.rank)) return true;
|
||||||
);
|
if (!hasDashboard) return false;
|
||||||
|
return slug.endsWith(".view") || slug === PERMS.ADMIN_DASHBOARD;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
|
|||||||
@@ -31,4 +31,15 @@ describe("production ACL migration", () => {
|
|||||||
expect(seedSql).toContain("ap.slug = 'admin.dashboard'");
|
expect(seedSql).toContain("ap.slug = 'admin.dashboard'");
|
||||||
expect(seedSql).toContain("ap.slug = 'mod.dashboard'");
|
expect(seedSql).toContain("ap.slug = 'mod.dashboard'");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("repairs full admin.* grants for ranks that already have dashboard", () => {
|
||||||
|
const repairSql = readFileSync(
|
||||||
|
resolve(migrationsDir, "0018_acl_repair_admin_nav_grants.sql"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
expect(repairSql).toContain("admin.dashboard");
|
||||||
|
expect(repairSql).toContain("ap.slug LIKE 'admin.%'");
|
||||||
|
expect(repairSql).toContain("pr.id >= 6");
|
||||||
|
expect(repairSql).toContain("pr.id >= 7");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user