interface GroupPermissionDetail { position: number name: string } interface GroupMetaPermissionDetail { permissions: Array name: string } type GroupPermissionsDef = Record type GroupMetaPermissionsDef = Record interface GroupDef { size: number permissions: GroupPermissionsDef meta_permissions?: GroupMetaPermissionsDef } type PermissionDef = Record interface PermissionDescription { key: string value: number name: string } const _display_names = new Map() import raw from "./permissions.config.json" const config = raw as const const __validate_config = (config: unknown): config is PermissionDef => { const error = (message: string) => new Error("Failed to parse permissions.config.json: " + message) if (typeof config !== "object" || config === null) throw error("configuration is not an object or null.") for (const [group, definition] of Object.entries(config)) { if (typeof definition !== "object") throw error(`definition for ${group} is not an object (is ${typeof definition})`) if (definition == null) throw error(`definition for ${group} is null`) if (typeof definition.size !== "number") throw error(`type of size in group ${group} is not number (is ${typeof definition.size})`) if (typeof definition.permissions !== "object") throw error(`definition of permissions for group ${group} is not an object`) for (const [name, detail] of Object.entries(definition.permissions)) { if (typeof detail !== "object" || detail == null) throw error(`definition for permission ${name} has to be an object`) if (typeof detail.position !== "number") throw error(`position of ${name} in group ${group} is not a number (is ${typeof detail.position})`) if (detail.position >= definition.size) throw error(`position ${position} of permission ${name} in group ${group} is out of bounds (size is ${definition.size})`) } if (definition.meta_permissions !== undefined) { const permissions = Object.keys(definition.permissions) if (typeof definition.meta_permissions !== "object") throw error(`meta_permissions of group ${group} is not an object (is ${typeof definition.meta_permissions})`) for (const [name, parts] of Object.entries(definition.meta_permissions)) { if (Object.keys(definition.permissions).includes(name)) throw error(`meta permission ${name} uses the same name as the permission`) if (typeof parts !== "object" || parts == null) throw error(`definition of meta permission ${name} in group ${group} is not an object`) if (typeof parts.permissions !== "object" || !(parts.permissions instanceof Array)) throw error(`definition of meta permission ${name} has to include a key "permissions" with a value of type Array`) for (const partial of (parts.permissions as Array)) { if (!permissions.includes(partial)) throw error(`permission ${partial} of definition of meta permission ${name} in group ${group} is not a permission of this group`) } } } } return true } type PermissionGroups = keyof typeof config function toPermissionObj(config: PermissionDef): Record> { let curr_pos = 0 const obj: Record> = {} for (const [name, definition] of Object.entries(config)) { obj[name] = {} for (const [permission, detail] of Object.entries(definition.permissions)) { const mask = 1 << (curr_pos + detail.position) obj[name][permission] = mask _display_names.set(mask, detail.name) } for (const [meta, detail] of Object.entries(definition?.meta_permissions ?? {})) { let mask = 0; for (const permission of detail.permissions) { mask |= 1 << definition.permissions[permission].position } obj[name][meta] = mask << curr_pos _display_names.set(mask, detail.name) } curr_pos += definition.size } return obj } __validate_config(config) const _deconstruct = (permission: number): Array => { const parts: Array = [] let mask = 1 while (permission >= mask) { const perm = permission & mask if (perm > 0) parts.push(perm) mask <<= 1 } return parts } const _to_display_name = (permission: number): string => { const name = _display_names.get(permission) if (name != undefined) return name const names = _deconstruct(permission).map((value) => _display_names.get(value)).filter((value) => value != undefined) return names.join(" | ") } export default { ...toPermissionObj(config), ALL: (permission_group: Record): number => Object.values(permission_group).reduce((pv: number, cv: number) => pv | cv), has: (user_permissions: number, permissions: number): boolean => (user_permissions & permissions) == permissions, any: (user_permissions: number, permissions: number): boolean => (user_permissions & permissions) > 0, iterate: (permission_group: Record): Array => Object.entries(permission_group).map(([key, value]) => ({ key: key, value: value, name: _to_display_name(value)})), is_meta: (permission: number) => permission != 0 && ((permission & (permission - 1)) != 0), deconstruct: _deconstruct, display_name: _to_display_name }