UNPKG

@casl/ability

Version:

CASL is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access

1 lines 17.4 kB
{"version":3,"file":"index.mjs","sources":["../../../src/utils.ts","../../../src/extra/packRules.ts","../../../src/extra/permittedFieldsOf.ts","../../../src/extra/rulesToFields.ts","../../../src/extra/rulesToQuery.ts"],"sourcesContent":["import { AnyObject, Subject, SubjectType, SubjectClass, ForcedSubject, AliasesMap } from './types';\n\nexport function wrapArray<T>(value: T[] | T): T[] {\n return Array.isArray(value) ? value : [value];\n}\n\nexport function setByPath(object: AnyObject, path: string, value: unknown): void {\n let ref = object;\n let lastKey = path;\n\n if (path.indexOf('.') !== -1) {\n const keys = path.split('.');\n\n lastKey = keys.pop()!;\n ref = keys.reduce((res, prop) => {\n res[prop] = res[prop] || {};\n return res[prop] as AnyObject;\n }, object);\n }\n\n ref[lastKey] = value;\n}\n\nconst TYPE_FIELD = '__caslSubjectType__';\nexport function setSubjectType<\n T extends string,\n U extends Record<PropertyKey, any>\n>(type: T, object: U): U & ForcedSubject<T> {\n if (object) {\n if (!Object.hasOwn(object, TYPE_FIELD)) {\n Object.defineProperty(object, TYPE_FIELD, { value: type });\n } else if (type !== object[TYPE_FIELD]) {\n throw new Error(`Trying to cast object to subject type ${type} but previously it was casted to ${object[TYPE_FIELD]}`);\n }\n }\n\n return object as U & ForcedSubject<T>;\n}\n\nexport const isSubjectType = (value: unknown): value is SubjectType => {\n const type = typeof value;\n return type === 'string' || type === 'function';\n};\n\nconst getSubjectClassName = (value: SubjectClass) => value.modelName || value.name;\nexport function getSubjectTypeName(value: SubjectType) {\n return typeof value === 'string' ? value : getSubjectClassName(value);\n}\n\nexport function detectSubjectType(object: Exclude<Subject, SubjectType>): string {\n if (Object.hasOwn(object, TYPE_FIELD)) {\n return object[TYPE_FIELD];\n }\n\n return getSubjectClassName(object.constructor as SubjectClass);\n}\n\nexport const DETECT_SUBJECT_TYPE_STRATEGY = {\n function: (object: Exclude<Subject, SubjectType>) => object.constructor as SubjectClass,\n string: detectSubjectType\n};\n\ntype AliasMerge = (actions: string[], action: string | string[]) => string[];\nfunction expandActions(aliasMap: AliasesMap, rawActions: string | string[], merge: AliasMerge) {\n let actions = wrapArray(rawActions);\n let i = 0;\n\n while (i < actions.length) {\n const action = actions[i++];\n\n if (Object.hasOwn(aliasMap, action)) {\n actions = merge(actions, aliasMap[action]);\n }\n }\n\n return actions;\n}\n\nfunction findDuplicate(actions: string[], actionToFind: string | string[]) {\n if (typeof actionToFind === 'string' && actions.indexOf(actionToFind) !== -1) {\n return actionToFind;\n }\n\n for (let i = 0; i < actionToFind.length; i++) {\n if (actions.indexOf(actionToFind[i]) !== -1) return actionToFind[i];\n }\n\n return null;\n}\n\nconst defaultAliasMerge: AliasMerge = (actions, action) => actions.concat(action);\nfunction validateForCycles(aliasMap: AliasesMap, reservedAction: string) {\n if (reservedAction in aliasMap) {\n throw new Error(`Cannot use \"${reservedAction}\" as an alias because it's reserved action.`);\n }\n\n const keys = Object.keys(aliasMap);\n const mergeAliasesAndDetectCycles: AliasMerge = (actions, action) => {\n const duplicate = findDuplicate(actions, action);\n if (duplicate) throw new Error(`Detected cycle ${duplicate} -> ${actions.join(', ')}`);\n\n const isUsingReservedAction = typeof action === 'string' && action === reservedAction\n || actions.indexOf(reservedAction) !== -1\n || Array.isArray(action) && action.indexOf(reservedAction) !== -1;\n if (isUsingReservedAction) throw new Error(`Cannot make an alias to \"${reservedAction}\" because this is reserved action`);\n\n return actions.concat(action);\n };\n\n for (let i = 0; i < keys.length; i++) {\n expandActions(aliasMap, keys[i], mergeAliasesAndDetectCycles);\n }\n}\n\nexport type AliasResolverOptions = { skipValidate?: boolean; anyAction?: string };\nexport function createAliasResolver(aliasMap: AliasesMap, options?: AliasResolverOptions) {\n if (!options || options.skipValidate !== false) {\n validateForCycles(aliasMap, options && options.anyAction || 'manage');\n }\n\n return (action: string | string[]) => expandActions(aliasMap, action, defaultAliasMerge);\n}\n\nfunction copyArrayTo<T>(dest: T[], target: T[], start: number) {\n for (let i = start; i < target.length; i++) {\n dest.push(target[i]);\n }\n}\n\nexport function mergePrioritized<T extends { priority: number }>(\n array?: T[],\n anotherArray?: T[]\n): T[] {\n if (!array || !array.length) {\n return anotherArray || [];\n }\n\n if (!anotherArray || !anotherArray.length) {\n return array || [];\n }\n\n let i = 0;\n let j = 0;\n const merged: T[] = [];\n\n while (i < array.length && j < anotherArray.length) {\n if (array[i].priority < anotherArray[j].priority) {\n merged.push(array[i]);\n i++;\n } else {\n merged.push(anotherArray[j]);\n j++;\n }\n }\n\n copyArrayTo(merged, array, i);\n copyArrayTo(merged, anotherArray, j);\n\n return merged;\n}\n\nexport function getOrDefault<K, V>(map: Map<K, V>, key: K, defaultValue: () => V) {\n let value = map.get(key);\n\n if (!value) {\n value = defaultValue();\n map.set(key, value);\n }\n\n return value;\n}\n\nexport const identity = <T>(x: T) => x;\n","\nimport { RawRule } from '../RawRule';\nimport { SubjectType } from '../types';\nimport { wrapArray } from '../utils';\n\nconst joinIfArray = (value: string | string[]) => Array.isArray(value) ? value.join(',') : value;\n\nexport type PackRule<T extends RawRule<any, any>> =\n [string, string] |\n [string, string, T['conditions']] |\n [string, string, T['conditions'] | 0, 1] |\n [string, string, T['conditions'] | 0, 1 | 0, string] |\n [string, string, T['conditions'] | 0, 1 | 0, string | 0, string];\n\nexport type PackSubjectType<T extends SubjectType> = (type: T) => string;\n\nexport function packRules<T extends RawRule<any, any>>(\n rules: T[],\n packSubject?: PackSubjectType<T['subject']>\n): PackRule<T>[] {\n return rules.map((rule) => { // eslint-disable-line\n const packedRule: PackRule<T> = [\n joinIfArray((rule as any).action || (rule as any).actions),\n typeof packSubject === 'function'\n ? wrapArray(rule.subject).map(packSubject).join(',')\n : joinIfArray(rule.subject),\n rule.conditions || 0,\n rule.inverted ? 1 : 0,\n rule.fields ? joinIfArray(rule.fields) : 0,\n rule.reason || ''\n ];\n\n while (packedRule.length > 0 && !packedRule[packedRule.length - 1]) packedRule.pop();\n\n return packedRule;\n });\n}\n\nexport type UnpackSubjectType<T extends SubjectType> = (type: string) => T;\n\nexport function unpackRules<T extends RawRule<any, any>>(\n rules: PackRule<T>[],\n unpackSubject?: UnpackSubjectType<T['subject']>\n): T[] {\n return rules.map(([action, subject, conditions, inverted, fields, reason]) => {\n const subjects = subject.split(',');\n const rule = {\n inverted: !!inverted,\n action: action.split(','),\n subject: typeof unpackSubject === 'function'\n ? subjects.map(unpackSubject)\n : subjects\n } as T;\n\n if (conditions) rule.conditions = conditions;\n if (fields) rule.fields = fields.split(',');\n if (reason) rule.reason = reason;\n\n return rule;\n });\n}\n","import { AnyAbility } from '../PureAbility';\nimport { Rule } from '../Rule';\nimport { RuleOf } from '../RuleIndex';\nimport { Subject, SubjectType } from '../types';\n\nexport type GetRuleFields<R extends Rule<any, any>> = (rule: R) => string[];\n\nexport interface PermittedFieldsOptions<T extends AnyAbility> {\n fieldsFrom: GetRuleFields<RuleOf<T>>\n}\n\nexport function permittedFieldsOf<T extends AnyAbility>(\n ability: T,\n action: Parameters<T['can']>[0],\n subject: Parameters<T['can']>[1],\n options: PermittedFieldsOptions<T>\n): string[] {\n const subjectType = ability.detectSubjectType(subject);\n const rules = ability.possibleRulesFor(action, subjectType);\n const uniqueFields = new Set<string>();\n const deleteItem = uniqueFields.delete.bind(uniqueFields);\n const addItem = uniqueFields.add.bind(uniqueFields);\n let i = rules.length;\n\n while (i--) {\n const rule = rules[i];\n if (rule.matchesConditions(subject)) {\n const toggle = rule.inverted ? deleteItem : addItem;\n options.fieldsFrom(rule).forEach(toggle);\n }\n }\n\n return Array.from(uniqueFields);\n}\n\nexport type GetSubjectTypeAllFieldsExtractor = (subjectType: SubjectType) => string[];\n\n/**\n * Helper class to make custom `accessibleFieldsBy` helper function\n */\nexport class AccessibleFields<T extends Subject> {\n constructor(\n private readonly _ability: AnyAbility,\n private readonly _action: string,\n private readonly _getAllFields: GetSubjectTypeAllFieldsExtractor\n ) {}\n\n /**\n * Returns accessible fields for Model type\n */\n ofType(subjectType: Extract<T, SubjectType>): string[] {\n return permittedFieldsOf(this._ability, this._action, subjectType, {\n fieldsFrom: this._getRuleFields(subjectType)\n });\n }\n\n /**\n * Returns accessible fields for particular document\n */\n of(subject: Exclude<T, SubjectType>): string[] {\n return permittedFieldsOf(this._ability, this._action, subject, {\n fieldsFrom: this._getRuleFields(this._ability.detectSubjectType(subject))\n });\n }\n\n private _getRuleFields(type: SubjectType): GetRuleFields<RuleOf<AnyAbility>> {\n return (rule) => (rule.fields || this._getAllFields(type));\n }\n}\n","import { PureAbility } from '../PureAbility';\nimport { AnyObject, ExtractSubjectType } from '../types';\nimport { setByPath } from '../utils';\n\n/**\n * Extracts rules condition values into an object of default values\n */\nexport function rulesToFields<T extends PureAbility<any, AnyObject>>(\n ability: T,\n action: Parameters<T['rulesFor']>[0],\n subjectType: ExtractSubjectType<Parameters<T['rulesFor']>[1]>,\n): AnyObject {\n return ability.rulesFor(action, subjectType)\n .reduce((values, rule) => {\n if (rule.inverted || !rule.conditions) {\n return values;\n }\n\n return Object.keys(rule.conditions).reduce((fields, fieldName) => {\n const value = rule.conditions![fieldName];\n\n if (!value || (value as any).constructor !== Object) {\n setByPath(fields, fieldName, value);\n }\n\n return fields;\n }, values);\n }, {} as AnyObject);\n}\n","import { CompoundCondition, Condition, buildAnd, buildOr } from '@ucast/mongo2js';\nimport { AnyAbility } from '../PureAbility';\nimport { Generics, RuleOf } from '../RuleIndex';\nimport { ExtractSubjectType } from '../types';\n\nexport type RuleToQueryConverter<T extends AnyAbility, R = object> = (rule: RuleOf<T>) => R;\nexport interface AbilityQuery<T = object> {\n $or?: T[]\n $and?: T[]\n}\n\nexport function rulesToQuery<T extends AnyAbility, R = object>(\n ability: T,\n action: Parameters<T['rulesFor']>[0],\n subjectType: ExtractSubjectType<Parameters<T['rulesFor']>[1]>,\n convert: RuleToQueryConverter<T, R>\n): AbilityQuery<R> | null {\n const $and: Generics<T>['conditions'][] = [];\n const $or: Generics<T>['conditions'][] = [];\n const rules = ability.rulesFor(action, subjectType);\n\n for (let i = 0; i < rules.length; i++) {\n const rule = rules[i];\n const list = rule.inverted ? $and : $or;\n\n if (!rule.conditions) {\n if (rule.inverted) {\n // stop if inverted rule without fields and conditions\n // Example:\n // can('read', 'Post', { id: 2 })\n // cannot('read', \"Post\")\n // can('read', 'Post', { id: 5 })\n break;\n } else {\n // if it allows reading all types then remove previous conditions\n // Example:\n // can('read', 'Post', { id: 1 })\n // can('read', 'Post')\n // cannot('read', 'Post', { status: 'draft' })\n return $and.length ? { $and } : {};\n }\n } else {\n list.push(convert(rule));\n }\n }\n\n // if there are no regular conditions and the where no rule without condition\n // then user is not allowed to perform this action on this subject type\n if (!$or.length) return null;\n return $and.length ? { $or, $and } : { $or };\n}\n\nfunction ruleToAST(rule: RuleOf<AnyAbility>): Condition {\n if (!rule.ast) {\n throw new Error(`Ability rule \"${JSON.stringify(rule)}\" does not have \"ast\" property. So, cannot be used to generate AST`);\n }\n\n return rule.inverted ? new CompoundCondition('not', [rule.ast]) : rule.ast;\n}\n\nexport function rulesToAST<T extends AnyAbility>(\n ability: T,\n action: Parameters<T['rulesFor']>[0],\n subjectType: ExtractSubjectType<Parameters<T['rulesFor']>[1]>,\n): Condition | null {\n const query = rulesToQuery(ability, action, subjectType, ruleToAST) as AbilityQuery<Condition>;\n\n if (query === null) {\n return null;\n }\n\n if (!query.$and) {\n return query.$or ? buildOr(query.$or) : buildAnd([]);\n }\n\n if (query.$or) {\n query.$and.push(buildOr(query.$or));\n }\n\n return buildAnd(query.$and);\n}\n"],"names":["wrapArray","value","Array","isArray","setByPath","object","path","ref","lastKey","indexOf","keys","split","pop","reduce","res","prop","joinIfArray","join","packRules","rules","packSubject","map","rule","packedRule","action","actions","subject","conditions","inverted","fields","reason","length","unpackRules","unpackSubject","subjects","permittedFieldsOf","ability","options","subjectType","detectSubjectType","possibleRulesFor","uniqueFields","Set","deleteItem","delete","bind","addItem","add","i","matchesConditions","toggle","fieldsFrom","forEach","from","AccessibleFields","constructor","_ability","_action","_getAllFields","this","ofType","_getRuleFields","of","type","rulesToFields","rulesFor","values","Object","fieldName","rulesToQuery","convert","$and","$or","list","push","ruleToAST","ast","Error","JSON","stringify","CompoundCondition","rulesToAST","query","buildOr","buildAnd"],"mappings":"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"}