diff --git a/src/lib/permissions.config.json b/src/lib/permissions.config.json new file mode 100644 index 0000000..59d34a7 --- /dev/null +++ b/src/lib/permissions.config.json @@ -0,0 +1,15 @@ +{ + "USERADMIN": { + "size": 8, + "permissions": { + "VIEW": 0, + "ADD": 1, + "DELETE": 2, + "EDIT": 3, + "EDIT_PASSWORD": 4 + }, + "meta_permissions": { + "MANAGE": ["VIEW", "ADD", "DELETE", "EDIT"] + } + }, +} diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts new file mode 100644 index 0000000..a687425 --- /dev/null +++ b/src/lib/permissions.ts @@ -0,0 +1,94 @@ + +type GroupPermissionsDef = Record + +interface GroupDef { + size: number + permissions: GroupPermissionsDef + meta_permissions?: Record> +} + +type PermissionDef = Record + + + + +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, position] of Object.entries(definition.permissions)) { + if (typeof position !== "number") + throw error(`position of ${name} in group ${group} is not a number (is ${typeof position})`) + if (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 instanceof Array) || parts === null) + throw error(`definition of meta permission ${name} in group ${group} is not an array`) + for (const partial of (parts 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, value] of Object.entries(definition.permissions)) { + obj[name][permission] = 1 << (curr_pos + value) + } + for (const [meta, permissions] of Object.entries(definition?.meta_permissions ?? {})) { + let mask = 0; + for (const permission in permissions) { + mask |= 1 << definition.permissions[permission] + } + + obj[name][meta] = mask << curr_pos + } + + curr_pos += definition.size + } + return obj +} + +__validate_config(config) + + +export default { + ...toPermissionObj(config), + + has: (user_permissions: number, permission: number): boolean => (user_permissions & permission) > 0 +}