Finish fine-grained ACL across remaining admin pages and actions.
Replace leftover requireStaff gates with module PERMS, drop hardcoded room rank thresholds, and expand contract tests so admin mutations cannot regress to dashboard-only checks. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
3c8a8ff888
commit
0e89d03940
63 files changed
+420
-163
No files matched your search
@@ -1,21 +1,14 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { join, relative } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const ROUTES: Array<[string, string]> = [
|
||||
["moderation", "PERMS.MODERATION_VIEW"],
|
||||
["moderation/actions", "PERMS.MODERATION_EDIT"],
|
||||
["moderation/cfh", "PERMS.MODERATION_VIEW"],
|
||||
["moderation/cfh/[id]", "PERMS.MODERATION_VIEW"],
|
||||
["moderation/team", "PERMS.MODERATION_VIEW"],
|
||||
["logs/audit", "PERMS.LOGS_VIEW"],
|
||||
["logs/chat", "PERMS.LOGS_VIEW"],
|
||||
["logs/commands", "PERMS.LOGS_VIEW"],
|
||||
["logs/trades", "PERMS.LOGS_VIEW"],
|
||||
["analytics", "PERMS.ANALYTICS_VIEW"],
|
||||
["analytics/activity", "PERMS.ANALYTICS_VIEW"],
|
||||
["analytics/economy", "PERMS.ANALYTICS_VIEW"],
|
||||
["devops", "PERMS.DEVOPS_VIEW"],
|
||||
["devops/errors", "PERMS.DEVOPS_VIEW"],
|
||||
["online", "PERMS.USERS_VIEW"],
|
||||
["commandocentrum", "PERMS.RCON_EXECUTE"],
|
||||
["users/edit/[id]", "PERMS.USERS_EDIT"],
|
||||
@@ -26,6 +19,43 @@ const ROUTES: Array<[string, string]> = [
|
||||
["wordfilter", "PERMS.WORDFILTER_VIEW"],
|
||||
["articles", "PERMS.NEWS_VIEW"],
|
||||
["shop", "PERMS.SHOP_VIEW"],
|
||||
["transactions", "PERMS.SHOP_VIEW"],
|
||||
["vouchers", "PERMS.SHOP_VIEW"],
|
||||
["vpn", "PERMS.SETTINGS_VIEW"],
|
||||
["ip", "PERMS.SETTINGS_VIEW"],
|
||||
["maintenance", "PERMS.SETTINGS_VIEW"],
|
||||
["alerts", "PERMS.NOTIFICATIONS_VIEW"],
|
||||
["tags", "PERMS.PAGES_VIEW"],
|
||||
["ads", "PERMS.PAGES_VIEW"],
|
||||
["media", "PERMS.PAGES_VIEW"],
|
||||
["photos", "PERMS.PAGES_VIEW"],
|
||||
["badges", "PERMS.CATALOG_VIEW"],
|
||||
["teams", "PERMS.USERS_VIEW"],
|
||||
["applications", "PERMS.USERS_VIEW"],
|
||||
["logs", "PERMS.LOGS_VIEW"],
|
||||
["catalog", "PERMS.CATALOG_VIEW"],
|
||||
["rooms/edit/[id]", "PERMS.ROOMS_EDIT"],
|
||||
];
|
||||
|
||||
const ACTION_GATES: Array<[string, string]> = [
|
||||
["admin-settings.ts", "PERMS.SETTINGS_EDIT"],
|
||||
["admin-theme.ts", "PERMS.SETTINGS_EDIT"],
|
||||
["admin-emulator.ts", "PERMS.SETTINGS_EDIT"],
|
||||
["admin-bans.ts", "PERMS.USERS_BAN"],
|
||||
["admin-wordfilter.ts", "PERMS.WORDFILTER_EDIT"],
|
||||
["admin-articles.ts", "PERMS.NEWS_EDIT"],
|
||||
["admin-shop.ts", "PERMS.SHOP_EDIT"],
|
||||
["admin-radio-autodj.ts", "PERMS.RADIO_EDIT"],
|
||||
["catalog.ts", "PERMS.CATALOG_EDIT"],
|
||||
["rooms.ts", "PERMS.ROOMS_EDIT"],
|
||||
["admin-users.ts", "PERMS.USERS_EDIT"],
|
||||
["admin-vpn.ts", "PERMS.SETTINGS_EDIT"],
|
||||
["admin-ads.ts", "PERMS.PAGES_EDIT"],
|
||||
["admin-vouchers.ts", "PERMS.SHOP_EDIT"],
|
||||
["admin-alerts.ts", "PERMS.NOTIFICATIONS_EDIT"],
|
||||
["admin-permissions.ts", "PERMS.PERMISSIONS_MANAGE"],
|
||||
["commandocentrum.ts", "PERMS.RCON_EXECUTE"],
|
||||
["translations.ts", "PERMS.SETTINGS_EDIT"],
|
||||
];
|
||||
|
||||
describe("admin operations route contract", () => {
|
||||
@@ -51,36 +81,42 @@ describe("admin operations route contract", () => {
|
||||
expect(readFileSync(path, "utf8"), path).toContain(permission);
|
||||
});
|
||||
|
||||
it("guards moderation mutations separately from page navigation", () => {
|
||||
const source = readFileSync("src/actions/moderation.ts", "utf8");
|
||||
expect(source).toContain("PERMS.MODERATION_EDIT");
|
||||
expect(source).toContain("adminAction");
|
||||
});
|
||||
|
||||
it("guards translation writes with SETTINGS_EDIT", () => {
|
||||
const source = readFileSync("src/actions/translations.ts", "utf8");
|
||||
expect(source).toContain("PERMS.SETTINGS_EDIT");
|
||||
expect(source).not.toContain("rank < 7");
|
||||
});
|
||||
|
||||
it("guards commandocentrum mutations with RCON_EXECUTE", () => {
|
||||
const source = readFileSync("src/actions/commandocentrum.ts", "utf8");
|
||||
expect(source).toContain("PERMS.RCON_EXECUTE");
|
||||
expect(source).not.toContain("requireStaff()");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["admin-settings.ts", "PERMS.SETTINGS_EDIT"],
|
||||
["admin-theme.ts", "PERMS.SETTINGS_EDIT"],
|
||||
["admin-emulator.ts", "PERMS.SETTINGS_EDIT"],
|
||||
["admin-bans.ts", "PERMS.USERS_BAN"],
|
||||
["admin-wordfilter.ts", "PERMS.WORDFILTER_EDIT"],
|
||||
["admin-articles.ts", "PERMS.NEWS_EDIT"],
|
||||
["admin-shop.ts", "PERMS.SHOP_EDIT"],
|
||||
["admin-radio-autodj.ts", "PERMS.RADIO_EDIT"],
|
||||
])("guards %s mutations with %s", (file, permission) => {
|
||||
it.each(ACTION_GATES)("guards %s with %s", (file, permission) => {
|
||||
const source = readFileSync(`src/actions/${file}`, "utf8");
|
||||
expect(source).toContain(permission);
|
||||
expect(source).not.toMatch(/await requireStaff\(\)/);
|
||||
expect(source).not.toMatch(/await requireStaffRateLimited\(\)/);
|
||||
});
|
||||
|
||||
it("has no requireStaff left in admin action modules", () => {
|
||||
const dir = "src/actions";
|
||||
const offenders: string[] = [];
|
||||
for (const name of readdirSync(dir)) {
|
||||
if (!name.endsWith(".ts") || name.endsWith(".test.ts")) continue;
|
||||
const source = readFileSync(join(dir, name), "utf8");
|
||||
if (/await requireStaff(RateLimited)?\(\)/.test(source)) {
|
||||
offenders.push(name);
|
||||
}
|
||||
}
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("has no requireStaff left in admin pages", () => {
|
||||
const root = "src/app/admin";
|
||||
const offenders: string[] = [];
|
||||
function walk(dir: string) {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(full);
|
||||
else if (entry.name === "page.tsx") {
|
||||
const source = readFileSync(full, "utf8");
|
||||
if (/await requireStaff\(/.test(source)) {
|
||||
offenders.push(relative(process.cwd(), full).replaceAll("\\", "/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(root);
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user