Introduce a dynamic RBAC system with a new 'roles' collection that grants granular permissions. Add hasPermission/requirePermission/loadUserPermissions utilities and a central permissions registry. Register the Roles collection in payload.config and add roleDocs relationship to Users.
616 lines
18 KiB
TypeScript
616 lines
18 KiB
TypeScript
/**
|
||
* Permission universe — the single source of truth for every permission
|
||
* recognized by the dynamic RBAC system.
|
||
*
|
||
* Permissions are hardcoded here in application code. The Payload admin panel
|
||
* reads this list when rendering the permission selector on the `roles`
|
||
* collection. Roles are dynamic (created/edited in the admin panel), but the
|
||
* set of assignable permissions is NOT — it can only grow by adding entries
|
||
* here.
|
||
*
|
||
* Format: `{collection-slug}:{create|read|update|delete}` for collection CRUD,
|
||
* plus domain-specific special permissions for feature areas that don't map
|
||
* cleanly to CRUD (e.g. `logistics:manage`, `discord:announce`).
|
||
*/
|
||
|
||
export interface PermissionOption {
|
||
/** Machine-readable key, e.g. `"users:create"`. */
|
||
value: string;
|
||
/** Human-readable label, e.g. `"Create"`. */
|
||
label: string | string[];
|
||
}
|
||
|
||
export interface PermissionGroup {
|
||
/** Group label shown as a header in the admin UI, e.g. `"Users"`. */
|
||
group: string;
|
||
/** Permissions within this group. */
|
||
permissions: PermissionOption[];
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Permission groups
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const PERMISSION_GROUPS: PermissionGroup[] = [
|
||
{
|
||
group: "System",
|
||
permissions: [{ value: "system:admin-access", label: "Access Admin Panel" }],
|
||
},
|
||
|
||
// ---- Users ---------------------------------------------------------------
|
||
{
|
||
group: "Users",
|
||
permissions: [
|
||
{ value: "users:create", label: "Create Users" },
|
||
{ value: "users:read", label: "Read Users" },
|
||
{ value: "users:update", label: "Update Users" },
|
||
{ value: "users:delete", label: "Delete Users" },
|
||
{ value: "users:unlock", label: "Unlock User Accounts" },
|
||
{ value: "users:assign-roles", label: "Assign Roles to Users" },
|
||
],
|
||
},
|
||
{
|
||
group: "Ranks",
|
||
permissions: [
|
||
{ value: "ranks:create", label: "Create" },
|
||
{ value: "ranks:read", label: "Read" },
|
||
{ value: "ranks:update", label: "Update" },
|
||
{ value: "ranks:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Profiles",
|
||
permissions: [
|
||
{ value: "profiles:create", label: "Create" },
|
||
{ value: "profiles:read", label: "Read" },
|
||
{ value: "profiles:update", label: "Update" },
|
||
{ value: "profiles:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Awards",
|
||
permissions: [
|
||
{ value: "awards:create", label: "Create" },
|
||
{ value: "awards:read", label: "Read" },
|
||
{ value: "awards:update", label: "Update" },
|
||
{ value: "awards:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Qualifications",
|
||
permissions: [
|
||
{ value: "qualifications:create", label: "Create" },
|
||
{ value: "qualifications:read", label: "Read" },
|
||
{ value: "qualifications:update", label: "Update" },
|
||
{ value: "qualifications:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Assignments",
|
||
permissions: [
|
||
{ value: "assignments:create", label: "Create" },
|
||
{ value: "assignments:read", label: "Read" },
|
||
{ value: "assignments:update", label: "Update" },
|
||
{ value: "assignments:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Experience",
|
||
permissions: [
|
||
{ value: "experience:create", label: "Create" },
|
||
{ value: "experience:read", label: "Read" },
|
||
{ value: "experience:update", label: "Update" },
|
||
{ value: "experience:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "User Notifications",
|
||
permissions: [
|
||
{ value: "user-notifications:create", label: "Create" },
|
||
{ value: "user-notifications:read", label: "Read" },
|
||
{ value: "user-notifications:update", label: "Update" },
|
||
{ value: "user-notifications:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Intelligence --------------------------------------------------------
|
||
{
|
||
group: "Missions",
|
||
permissions: [
|
||
{ value: "missions:create", label: "Create" },
|
||
{ value: "missions:read", label: "Read" },
|
||
{ value: "missions:update", label: "Update" },
|
||
{ value: "missions:delete", label: "Delete" },
|
||
{ value: "missions:read-sensitive", label: "Read Sensitive Fields (server passwords)" },
|
||
],
|
||
},
|
||
{
|
||
group: "Mission Attendances",
|
||
permissions: [
|
||
{ value: "mission-attendances:create", label: "Create" },
|
||
{ value: "mission-attendances:read", label: "Read" },
|
||
{ value: "mission-attendances:update", label: "Update" },
|
||
{ value: "mission-attendances:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Campaigns",
|
||
permissions: [
|
||
{ value: "campaigns:create", label: "Create" },
|
||
{ value: "campaigns:read", label: "Read" },
|
||
{ value: "campaigns:update", label: "Update" },
|
||
{ value: "campaigns:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Factions",
|
||
permissions: [
|
||
{ value: "factions:create", label: "Create" },
|
||
{ value: "factions:read", label: "Read" },
|
||
{ value: "factions:update", label: "Update" },
|
||
{ value: "factions:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Technologies",
|
||
permissions: [
|
||
{ value: "technologies:create", label: "Create" },
|
||
{ value: "technologies:read", label: "Read" },
|
||
{ value: "technologies:update", label: "Update" },
|
||
{ value: "technologies:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Logistics -----------------------------------------------------------
|
||
{
|
||
group: "Assets",
|
||
permissions: [
|
||
{ value: "assets:create", label: "Create" },
|
||
{ value: "assets:read", label: "Read" },
|
||
{ value: "assets:update", label: "Update" },
|
||
{ value: "assets:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Resources",
|
||
permissions: [
|
||
{ value: "resources:create", label: "Create" },
|
||
{ value: "resources:read", label: "Read" },
|
||
{ value: "resources:update", label: "Update" },
|
||
{ value: "resources:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Vehicles",
|
||
permissions: [
|
||
{ value: "vehicles:create", label: "Create" },
|
||
{ value: "vehicles:read", label: "Read" },
|
||
{ value: "vehicles:update", label: "Update" },
|
||
{ value: "vehicles:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Structures",
|
||
permissions: [
|
||
{ value: "structures:create", label: "Create" },
|
||
{ value: "structures:read", label: "Read" },
|
||
{ value: "structures:update", label: "Update" },
|
||
{ value: "structures:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Shipments",
|
||
permissions: [
|
||
{ value: "shipments:create", label: "Create" },
|
||
{ value: "shipments:read", label: "Read" },
|
||
{ value: "shipments:update", label: "Update" },
|
||
{ value: "shipments:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Banking -------------------------------------------------------------
|
||
{
|
||
group: "Bank Accounts",
|
||
permissions: [
|
||
{ value: "bank-accounts:create", label: "Create" },
|
||
{ value: "bank-accounts:read", label: "Read" },
|
||
{ value: "bank-accounts:update", label: "Update" },
|
||
{ value: "bank-accounts:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Bank Transactions",
|
||
permissions: [
|
||
{ value: "bank-transactions:create", label: "Create" },
|
||
{ value: "bank-transactions:read", label: "Read" },
|
||
{ value: "bank-transactions:update", label: "Update" },
|
||
{ value: "bank-transactions:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Ledger Entries",
|
||
permissions: [
|
||
{ value: "ledger-entries:create", label: "Create" },
|
||
{ value: "ledger-entries:read", label: "Read" },
|
||
{ value: "ledger-entries:update", label: "Update" },
|
||
{ value: "ledger-entries:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Locker --------------------------------------------------------------
|
||
{
|
||
group: "Locker Storages",
|
||
permissions: [
|
||
{ value: "locker-storages:create", label: "Create" },
|
||
{ value: "locker-storages:read", label: "Read" },
|
||
{ value: "locker-storages:update", label: "Update" },
|
||
{ value: "locker-storages:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Loadouts",
|
||
permissions: [
|
||
{ value: "loadouts:create", label: "Create" },
|
||
{ value: "loadouts:read", label: "Read" },
|
||
{ value: "loadouts:update", label: "Update" },
|
||
{ value: "loadouts:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Market --------------------------------------------------------------
|
||
{
|
||
group: "Market Listings",
|
||
permissions: [
|
||
{ value: "market-listings:create", label: "Create" },
|
||
{ value: "market-listings:read", label: "Read" },
|
||
{ value: "market-listings:update", label: "Update" },
|
||
{ value: "market-listings:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Market Negotiations",
|
||
permissions: [
|
||
{ value: "market-negotiations:create", label: "Create" },
|
||
{ value: "market-negotiations:read", label: "Read" },
|
||
{ value: "market-negotiations:update", label: "Update" },
|
||
{ value: "market-negotiations:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Helpdesk ------------------------------------------------------------
|
||
{
|
||
group: "Tickets",
|
||
permissions: [
|
||
{ value: "tickets:create", label: "Create" },
|
||
{ value: "tickets:read", label: "Read" },
|
||
{ value: "tickets:update", label: "Update" },
|
||
{ value: "tickets:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Game ----------------------------------------------------------------
|
||
{
|
||
group: "Game Structures",
|
||
permissions: [
|
||
{ value: "game-structures:create", label: "Create" },
|
||
{ value: "game-structures:read", label: "Read" },
|
||
{ value: "game-structures:update", label: "Update" },
|
||
{ value: "game-structures:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Game Vehicles",
|
||
permissions: [
|
||
{ value: "game-vehicles:create", label: "Create" },
|
||
{ value: "game-vehicles:read", label: "Read" },
|
||
{ value: "game-vehicles:update", label: "Update" },
|
||
{ value: "game-vehicles:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Game NPCs",
|
||
permissions: [
|
||
{ value: "game-npcs:create", label: "Create" },
|
||
{ value: "game-npcs:read", label: "Read" },
|
||
{ value: "game-npcs:update", label: "Update" },
|
||
{ value: "game-npcs:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Game Hard Resources",
|
||
permissions: [
|
||
{ value: "game-hard-resources:create", label: "Create" },
|
||
{ value: "game-hard-resources:read", label: "Read" },
|
||
{ value: "game-hard-resources:update", label: "Update" },
|
||
{ value: "game-hard-resources:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Game Event Logs",
|
||
permissions: [
|
||
{ value: "game-event-logs:create", label: "Create" },
|
||
{ value: "game-event-logs:read", label: "Read" },
|
||
{ value: "game-event-logs:update", label: "Update" },
|
||
{ value: "game-event-logs:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- World ---------------------------------------------------------------
|
||
{
|
||
group: "Maps",
|
||
permissions: [
|
||
{ value: "maps:create", label: "Create" },
|
||
{ value: "maps:read", label: "Read" },
|
||
{ value: "maps:update", label: "Update" },
|
||
{ value: "maps:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Narrative Events",
|
||
permissions: [
|
||
{ value: "narrative-events:create", label: "Create" },
|
||
{ value: "narrative-events:read", label: "Read" },
|
||
{ value: "narrative-events:update", label: "Update" },
|
||
{ value: "narrative-events:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Server --------------------------------------------------------------
|
||
{
|
||
group: "Mission Files",
|
||
permissions: [
|
||
{ value: "mission-files:create", label: "Create" },
|
||
{ value: "mission-files:read", label: "Read" },
|
||
{ value: "mission-files:update", label: "Update" },
|
||
{ value: "mission-files:delete", label: "Delete" },
|
||
],
|
||
},
|
||
{
|
||
group: "Mod Lists",
|
||
permissions: [
|
||
{ value: "mod-lists:create", label: "Create" },
|
||
{ value: "mod-lists:read", label: "Read" },
|
||
{ value: "mod-lists:update", label: "Update" },
|
||
{ value: "mod-lists:delete", label: "Delete" },
|
||
],
|
||
},
|
||
|
||
// ---- Globals -------------------------------------------------------------
|
||
{
|
||
group: "Game Rules (Global)",
|
||
permissions: [
|
||
{ value: "game-rules:read", label: "Read" },
|
||
{ value: "game-rules:update", label: "Update" },
|
||
],
|
||
},
|
||
{
|
||
group: "Shims (Global)",
|
||
permissions: [
|
||
{ value: "shims:read", label: "Read" },
|
||
{ value: "shims:update", label: "Update" },
|
||
],
|
||
},
|
||
|
||
// ---- Special / Feature-area ----------------------------------------------
|
||
{
|
||
group: "Logistics",
|
||
permissions: [{ value: "logistics:manage", label: "Manage Logistics (manager bypass)" }],
|
||
},
|
||
{
|
||
group: "Intelligence",
|
||
permissions: [
|
||
{ value: "intelligence:manage", label: "Manage Intelligence (manager bypass)" },
|
||
{ value: "profiles:intel_excerpt:update", label: ["Profile", "Intel Excerpt", "Update"] },
|
||
],
|
||
},
|
||
{
|
||
group: "Banking",
|
||
permissions: [{ value: "banking:manage", label: "Manage Banking (manager bypass)" }],
|
||
},
|
||
{
|
||
group: "Helpdesk",
|
||
permissions: [{ value: "tickets:staff", label: "Staff Helpdesk Tickets" }],
|
||
},
|
||
{
|
||
group: "Discord Bot",
|
||
permissions: [
|
||
{ value: "discord:staff", label: "Bot Staff (isStaff)" },
|
||
{ value: "discord:announce", label: "Post Announcements (/announce)" },
|
||
],
|
||
},
|
||
];
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Derived exports
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/** Flat array of all permission value strings, as a const tuple for type inference. */
|
||
export const ALL_PERMISSION_VALUES = PERMISSION_GROUPS.flatMap((g) =>
|
||
g.permissions.map((p) => p.value),
|
||
) as unknown as readonly [
|
||
"system:admin-access",
|
||
"users:create",
|
||
"users:read",
|
||
"users:update",
|
||
"users:delete",
|
||
"users:unlock",
|
||
"users:assign-roles",
|
||
"ranks:create",
|
||
"ranks:read",
|
||
"ranks:update",
|
||
"ranks:delete",
|
||
"profiles:create",
|
||
"profiles:read",
|
||
"profiles:update",
|
||
"profiles:delete",
|
||
"awards:create",
|
||
"awards:read",
|
||
"awards:update",
|
||
"awards:delete",
|
||
"qualifications:create",
|
||
"qualifications:read",
|
||
"qualifications:update",
|
||
"qualifications:delete",
|
||
"assignments:create",
|
||
"assignments:read",
|
||
"assignments:update",
|
||
"assignments:delete",
|
||
"experience:create",
|
||
"experience:read",
|
||
"experience:update",
|
||
"experience:delete",
|
||
"user-notifications:create",
|
||
"user-notifications:read",
|
||
"user-notifications:update",
|
||
"user-notifications:delete",
|
||
"missions:create",
|
||
"missions:read",
|
||
"missions:update",
|
||
"missions:delete",
|
||
"missions:read-sensitive",
|
||
"mission-attendances:create",
|
||
"mission-attendances:read",
|
||
"mission-attendances:update",
|
||
"mission-attendances:delete",
|
||
"campaigns:create",
|
||
"campaigns:read",
|
||
"campaigns:update",
|
||
"campaigns:delete",
|
||
"factions:create",
|
||
"factions:read",
|
||
"factions:update",
|
||
"factions:delete",
|
||
"technologies:create",
|
||
"technologies:read",
|
||
"technologies:update",
|
||
"technologies:delete",
|
||
"assets:create",
|
||
"assets:read",
|
||
"assets:update",
|
||
"assets:delete",
|
||
"resources:create",
|
||
"resources:read",
|
||
"resources:update",
|
||
"resources:delete",
|
||
"vehicles:create",
|
||
"vehicles:read",
|
||
"vehicles:update",
|
||
"vehicles:delete",
|
||
"structures:create",
|
||
"structures:read",
|
||
"structures:update",
|
||
"structures:delete",
|
||
"shipments:create",
|
||
"shipments:read",
|
||
"shipments:update",
|
||
"shipments:delete",
|
||
"bank-accounts:create",
|
||
"bank-accounts:read",
|
||
"bank-accounts:update",
|
||
"bank-accounts:delete",
|
||
"bank-transactions:create",
|
||
"bank-transactions:read",
|
||
"bank-transactions:update",
|
||
"bank-transactions:delete",
|
||
"ledger-entries:create",
|
||
"ledger-entries:read",
|
||
"ledger-entries:update",
|
||
"ledger-entries:delete",
|
||
"locker-storages:create",
|
||
"locker-storages:read",
|
||
"locker-storages:update",
|
||
"locker-storages:delete",
|
||
"loadouts:create",
|
||
"loadouts:read",
|
||
"loadouts:update",
|
||
"loadouts:delete",
|
||
"market-listings:create",
|
||
"market-listings:read",
|
||
"market-listings:update",
|
||
"market-listings:delete",
|
||
"market-negotiations:create",
|
||
"market-negotiations:read",
|
||
"market-negotiations:update",
|
||
"market-negotiations:delete",
|
||
"tickets:create",
|
||
"tickets:read",
|
||
"tickets:update",
|
||
"tickets:delete",
|
||
"game-structures:create",
|
||
"game-structures:read",
|
||
"game-structures:update",
|
||
"game-structures:delete",
|
||
"game-vehicles:create",
|
||
"game-vehicles:read",
|
||
"game-vehicles:update",
|
||
"game-vehicles:delete",
|
||
"game-npcs:create",
|
||
"game-npcs:read",
|
||
"game-npcs:update",
|
||
"game-npcs:delete",
|
||
"game-hard-resources:create",
|
||
"game-hard-resources:read",
|
||
"game-hard-resources:update",
|
||
"game-hard-resources:delete",
|
||
"game-event-logs:create",
|
||
"game-event-logs:read",
|
||
"game-event-logs:update",
|
||
"game-event-logs:delete",
|
||
"maps:create",
|
||
"maps:read",
|
||
"maps:update",
|
||
"maps:delete",
|
||
"narrative-events:create",
|
||
"narrative-events:read",
|
||
"narrative-events:update",
|
||
"narrative-events:delete",
|
||
"mission-files:create",
|
||
"mission-files:read",
|
||
"mission-files:update",
|
||
"mission-files:delete",
|
||
"mod-lists:create",
|
||
"mod-lists:read",
|
||
"mod-lists:update",
|
||
"mod-lists:delete",
|
||
"game-rules:read",
|
||
"game-rules:update",
|
||
"shims:read",
|
||
"shims:update",
|
||
"logistics:manage",
|
||
"banking:manage",
|
||
"tickets:staff",
|
||
"discord:staff",
|
||
"discord:announce",
|
||
"intelligence:manage",
|
||
"profiles:intel_excerpt:update",
|
||
];
|
||
|
||
/**
|
||
* Union type of every valid permission string.
|
||
* Use this to type-check permission arguments at compile time.
|
||
*/
|
||
export type Permission = (typeof ALL_PERMISSION_VALUES)[number];
|
||
|
||
/**
|
||
* Payload `select` field options, flattened with group-prefixed labels.
|
||
* e.g. `{ label: "Users › Create Users", value: "users:create" }`
|
||
*/
|
||
export const permissionSelectOptions: { label: string; value: string }[] =
|
||
PERMISSION_GROUPS.flatMap((g) =>
|
||
g.permissions.map((p) => {
|
||
let label = p.label;
|
||
if (Array.isArray(p.label)) label = p.label.join(" › ");
|
||
return {
|
||
label: `${g.group} › ${label}`,
|
||
value: p.value,
|
||
};
|
||
}),
|
||
);
|
||
|
||
/**
|
||
* Guard function — returns true if the given string is a valid permission.
|
||
* Useful for runtime validation of permission strings from DB or API input.
|
||
*/
|
||
export function isPermission(value: string): value is Permission {
|
||
return (ALL_PERMISSION_VALUES as readonly string[]).includes(value);
|
||
}
|