export enum ApiPermissions { Owner = "OWNER", ManageAccess = "MANAGE_ACCESS", EditConfig = "EDIT_CONFIG", ReadConfig = "READ_CONFIG", ViewGuild = "VIEW_GUILD", } const reverseApiPermissions = Object.entries(ApiPermissions).reduce((map, [key, value]) => { map[value] = key; return map; }, {}); export const permissionNames = { [ApiPermissions.Owner]: "Server owner", [ApiPermissions.ManageAccess]: "Bot manager", [ApiPermissions.EditConfig]: "Bot operator", [ApiPermissions.ReadConfig]: "Read config", [ApiPermissions.ViewGuild]: "View server", }; export type TPermissionHierarchy = Array; // prettier-ignore export const permissionHierarchy: TPermissionHierarchy = [ [ApiPermissions.Owner, [ [ApiPermissions.ManageAccess, [ [ApiPermissions.EditConfig, [ [ApiPermissions.ReadConfig, [ ApiPermissions.ViewGuild, ]], ]], ]], ]], ]; export function permissionArrToSet(permissions: string[]): Set { return new Set(permissions.filter((p) => reverseApiPermissions[p])) as Set; } /** * Checks whether granted permissions include the specified permission, taking into account permission hierarchy i.e. * that in the case of nested permissions, having a top level permission implicitly grants you any permissions nested * under it as well */ export function hasPermission(grantedPermissions: Set, permissionToCheck: ApiPermissions): boolean { // Directly granted if (grantedPermissions.has(permissionToCheck)) { return true; } // Check by hierarchy if (checkTreeForPermission(permissionHierarchy, grantedPermissions, permissionToCheck)) { return true; } return false; } function checkTreeForPermission( tree: TPermissionHierarchy, grantedPermissions: Set, permission: ApiPermissions, ): boolean { for (const item of tree) { const [perm, nested] = Array.isArray(item) ? item : [item]; // Top-level permission granted, implicitly grant all nested permissions as well if (grantedPermissions.has(perm)) { // Permission we were looking for was found nested under this permission -> granted if (nested && treeIncludesPermission(nested, permission)) { return true; } // Permission we were looking for was not found nested under this permission // Since direct grants are not handled by this function, we can skip any further checks for this nested tree continue; } // Top-level permission not granted, check further nested permissions if (nested && checkTreeForPermission(nested, grantedPermissions, permission)) { return true; } } return false; } function treeIncludesPermission(tree: TPermissionHierarchy, permission: ApiPermissions): boolean { for (const item of tree) { const [perm, nested] = Array.isArray(item) ? item : [item]; if (perm === permission) { return true; } const nestedResult = nested && treeIncludesPermission(nested, permission); if (nestedResult) { return true; } } return false; }