UNPKG

@bemedev/permissions

Version:

A library for managing permissions

209 lines (206 loc) 7.86 kB
import { common } from '@bemedev/core'; import { DELIMITER } from './constants.js'; class Machine { __config; #ressources; #implementation; #roles; constructor(__config) { this.__config = __config; this.#ressources = Object.freeze(this.__config.ressources); this.#roles = Object.freeze(this.__config.roles); } get config() { return Object.freeze(this.__config); } get roles() { return Object.freeze(this.#roles); } get implementation() { return Object.freeze(this.#implementation); } get __user() { return common.typings.dynamic(); } get ressources() { return this.#ressources; } getPriority(role) { return this.#roles[role]; } #sortRoles = (...roles) => { return roles.sort((a, b) => { return this.getPriority(a) - this.getPriority(b); }); }; #reverseRoles = (...roles) => { return roles.sort((a, b) => { return this.getPriority(b) - this.getPriority(a); }); }; sortRoles = (order, ...roles) => { if (order === 'asc') return this.#sortRoles(...roles); return this.#reverseRoles(...roles); }; #implements = (implementation) => { return (this.#implementation = implementation); }; /** * @deprecated * * Implements the machine with the provided implementation. * This method is used to set the implementation for the machine, allowing it to handle permissions * based on the provided implementation logic. * @param implementation The implementation logic to be set for the machine. * @returns A new instance of the machine with the provided implementation. */ __implements = (implementation) => { const _new = new Machine(this.__config); _new.#implements(implementation); return _new; }; #hasUserPermissions = ({ performer, owner, data, action, ressource, }) => { const sortedRoles = this.#reverseRoles(...performer.roles); const collecteds = []; sortedRoles.forEach(role => { const key = `${String(role)}${DELIMITER}${ressource}${DELIMITER}${action}`; const permission = this.#implementation[key]; if (common.castings.is.undefined(permission)) return; if (typeof permission === 'function') { const result = permission({ owner, performer, data, }); return collecteds.push(result); } return collecteds.push(permission); }); if (collecteds.length === 0) return false; return Machine.reduceCollection('or', ...collecteds); }; #hasDataPermissions = (performer, action, extra) => { const permissions = this.#extractDataPermissions(extra); const sortedRoles = this.#reverseRoles(...performer.roles); if (permissions === true) return true; const result1 = permissions[`user:${performer.__id}`]?.[action]; const alreadyTrue = result1 === true; if (alreadyTrue) return true; const collecteds = []; if (common.castings.is.defined(result1)) collecteds.push(result1); sortedRoles.forEach(role => { const _role = String(role); const value1 = permissions[`role:${_role}`]?.[action]; if (value1) collecteds.push(value1); }); if (collecteds.length === 0) return true; return Machine.reduceCollection('or', ...collecteds); }; hasPermisions = args => { const userPermissions = this.#hasUserPermissions(args); const dataPermissions = this.#hasDataPermissions(args.performer, args.action, args.data?.__extraPermissions); const strategy = this.#ressources[args.ressource].__strategy || 'bypass'; return Machine.reduceCollection(strategy, userPermissions, dataPermissions); }; #extractDataPermissions = (permissions) => { if (!permissions) return true; const entries = common.castings.unknown(Object.entries(permissions)); const allValuesAreNotDefineds = entries.every(([, value]) => common.castings.is.undefined(value)); if (allValuesAreNotDefineds) return true; const out = {}; entries.forEach(([action, _permissions]) => { if (common.castings.is.undefined(_permissions)) return; const { allow, disallow } = _permissions; if (allow) { const allowEntries = Object.entries(allow); allowEntries.forEach(([dataKey, userIds]) => { userIds.forEach(userId => { if (!out[userId]) { out[userId] = {}; } if (!out[userId][action]) { out[userId][action] = []; } const currentValue = out[userId][action]; if (dataKey === '**') { out[userId][action] = true; } else if (Array.isArray(currentValue)) { if (!currentValue.includes(dataKey)) { currentValue.push(dataKey); } } }); }); } if (disallow) { const disallowEntries = Object.entries(disallow); disallowEntries.forEach(([dataKey, userIds]) => { if (userIds.length === 0) return; userIds.forEach(userId => { if (!out[userId]) { out[userId] = {}; } if (!out[userId][action]) { out[userId][action] = []; } const currentValue = out[userId][action]; if (dataKey === '**') { out[userId][action] = false; } else if (Array.isArray(currentValue)) { // Remove from allowed if it was there (disallow takes precedence) const index = currentValue.indexOf(dataKey); if (index > -1) { currentValue.splice(index, 1); } } }); }); } }); return out; }; static reduceCollection = (strategy, ...collecteds) => { if (!strategy || strategy === 'bypass') return collecteds[0]; const isOr = strategy === 'or'; let out; for (const result of collecteds) { if (Array.isArray(out)) { if (Array.isArray(result)) { if (isOr) out = [...new Set([...out, ...result])]; else { out = out.filter(value => result.includes(value)); } } if (!isOr && result === false) return false; continue; } if (result === isOr) return isOr; out = result; } return out; }; } const createMachine = (config, implementation) => { const machine = new Machine(config).__implements(implementation); return machine; }; export { createMachine }; //# sourceMappingURL=machine.js.map