UNPKG

@casl/ability

Version:

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

3 lines (2 loc) 9.25 kB
import{createFactory as t,$eq as i,$ne as e,$lt as s,$lte as n,$gt as r,$gte as o,$in as c,$nin as u,$all as h,$size as l,$regex as a,$options as f,$elemMatch as d,$exists as y,eq as p,ne as b,lt as w,lte as g,gt as $,gte as A,within as m,nin as M,all as j,size as E,regex as v,elemMatch as x,exists as _,and as F}from"@ucast/mongo2js";function O(t){return Array.isArray(t)?t:[t]}const C="__caslSubjectType__";function R(t,i){if(i)if(!Object.hasOwn(i,C))Object.defineProperty(i,C,{value:t});else if(t!==i[C])throw new Error(`Trying to cast object to subject type ${t} but previously it was casted to ${i[C]}`);return i}const P=t=>{const i=typeof t;return i==="string"||i==="function"};const S=t=>t.modelName||t.name;function T(t){return typeof t==="string"?t:S(t)}function z(t){if(Object.hasOwn(t,C))return t[C];return S(t.constructor)}const B={function:t=>t.constructor,string:z};function q(t,i,e){let s=O(i);let n=0;while(n<s.length){const i=s[n++];if(Object.hasOwn(t,i))s=e(s,t[i])}return s}function D(t,i){if(typeof i==="string"&&t.indexOf(i)!==-1)return i;for(let e=0;e<i.length;e++)if(t.indexOf(i[e])!==-1)return i[e];return null}const Y=(t,i)=>t.concat(i);function k(t,i){if(i in t)throw new Error(`Cannot use "${i}" as an alias because it's reserved action.`);const e=Object.keys(t);const s=(t,e)=>{const s=D(t,e);if(s)throw new Error(`Detected cycle ${s} -> ${t.join(", ")}`);const n=typeof e==="string"&&e===i||t.indexOf(i)!==-1||Array.isArray(e)&&e.indexOf(i)!==-1;if(n)throw new Error(`Cannot make an alias to "${i}" because this is reserved action`);return t.concat(e)};for(let i=0;i<e.length;i++)q(t,e[i],s)}function L(t,i){if(!i||i.skipValidate!==false)k(t,i&&i.anyAction||"manage");return i=>q(t,i,Y)}function U(t,i,e){for(let s=e;s<i.length;s++)t.push(i[s])}function G(t,i){if(!t||!t.length)return i||[];if(!i||!i.length)return t||[];let e=0;let s=0;const n=[];while(e<t.length&&s<i.length)if(t[e].priority<i[s].priority){n.push(t[e]);e++}else{n.push(i[s]);s++}U(n,t,e);U(n,i,s);return n}function H(t,i,e){let s=t.get(i);if(!s){s=e();t.set(i,s)}return s}const I=t=>t;function J(t,i){if(Array.isArray(t.fields)&&!t.fields.length)throw new Error("`rawRule.fields` cannot be an empty array. https://bit.ly/390miLa");if(t.fields&&!i.fieldMatcher)throw new Error('You need to pass "fieldMatcher" option in order to restrict access by fields');if(t.conditions&&!i.conditionsMatcher)throw new Error('You need to pass "conditionsMatcher" option in order to restrict access by conditions')}class K{constructor(t,i,e=0){J(t,i);this.action=i.resolveAction(t.action);this.subject=t.subject;this.inverted=!!t.inverted;this.conditions=t.conditions;this.reason=t.reason;this.origin=t;this.fields=t.fields?O(t.fields):void 0;this.priority=e;this.t=i}i(){if(this.conditions&&!this.o)this.o=this.t.conditionsMatcher(this.conditions);return this.o}get ast(){const t=this.i();return t?t.ast:void 0}matchesConditions(t){if(!this.conditions)return true;if(!t||P(t))return!this.inverted;const i=this.i();return i(t)}matchesField(t){if(!this.fields)return true;if(!t)return!this.inverted;if(!this.u)this.u=this.t.fieldMatcher(this.fields);return this.u(t)}}function N(t,i){const e={value:t,prev:i,next:null};if(i)i.next=e;return e}function Q(t){if(t.next)t.next.prev=t.prev;if(t.prev)t.prev.next=t.next;t.next=t.prev=null}const V=t=>({value:t.value,prev:t.prev,next:t.next});const W=()=>({rules:[],merged:false});const X=()=>new Map;class Z{constructor(t=[],i={}){this.h=false;this.l=new Map;this.p={conditionsMatcher:i.conditionsMatcher,fieldMatcher:i.fieldMatcher,resolveAction:i.resolveAction||I};this.$=i.anyAction||"manage";this.A=i.anySubjectType||"all";this.m=t;this.M=!!i.detectSubjectType;this.j=i.detectSubjectType||z;this.v(t)}get rules(){return this.m}detectSubjectType(t){if(P(t))return t;if(!t)return this.A;return this.j(t)}update(t){const i={rules:t,ability:this,target:this};this._("update",i);this.m=t;this.v(t);this._("updated",i);return this}v(t){const i=new Map;let e;for(let s=t.length-1;s>=0;s--){const n=t.length-s-1;const r=new K(t[s],this.p,n);const o=O(r.action);const c=O(r.subject||this.A);if(!this.h&&r.fields)this.h=true;for(let t=0;t<c.length;t++){const s=H(i,c[t],X);if(e===void 0)e=typeof c[t];if(typeof c[t]!==e&&e!=="mixed")e="mixed";for(let t=0;t<o.length;t++)H(s,o[t],W).rules.push(r)}}this.l=i;if(e!=="mixed"&&!this.M){const t=B[e]||B.string;this.j=t}}possibleRulesFor(t,i=this.A){if(!P(i))throw new Error('"possibleRulesFor" accepts only subject types (i.e., string or class) as the 2nd parameter');const e=H(this.l,i,X);const s=H(e,t,W);if(s.merged)return s.rules;const n=t!==this.$&&e.has(this.$)?e.get(this.$).rules:void 0;let r=G(s.rules,n);if(i!==this.A)r=G(r,this.possibleRulesFor(t,this.A));s.rules=r;s.merged=true;return r}rulesFor(t,i,e){const s=this.possibleRulesFor(t,i);if(e&&typeof e!=="string")throw new Error("The 3rd, `field` parameter is expected to be a string. See https://stalniy.github.io/casl/en/api/casl-ability#can-of-pure-ability for details");if(!this.h)return s;return s.filter((t=>t.matchesField(e)))}actionsFor(t){if(!P(t))throw new Error('"actionsFor" accepts only subject types (i.e., string or class) as a parameter');const i=new Set;const e=this.l.get(t);if(e)Array.from(e.keys()).forEach((t=>i.add(t)));const s=t!==this.A?this.l.get(this.A):void 0;if(s)Array.from(s.keys()).forEach((t=>i.add(t)));return Array.from(i)}on(t,i){this.F=this.F||new Map;const e=this.F;const s=e.get(t)||null;const n=N(i,s);e.set(t,n);return()=>{const i=e.get(t);if(!n.next&&!n.prev&&i===n)e.delete(t);else if(n===i)e.set(t,n.prev);Q(n)}}_(t,i){if(!this.F)return;let e=this.F.get(t)||null;while(e!==null){const t=e.prev?V(e.prev):null;e.value(i);e=t}}}class PureAbility extends Z{can(t,i,e){const s=this.relevantRuleFor(t,i,e);return!!s&&!s.inverted}relevantRuleFor(t,i,e){const s=this.detectSubjectType(i);const n=this.rulesFor(t,s,e);for(let t=0,e=n.length;t<e;t++)if(n[t].matchesConditions(i))return n[t];return null}cannot(t,i,e){return!this.can(t,i,e)}}const tt={$eq:i,$ne:e,$lt:s,$lte:n,$gt:r,$gte:o,$in:c,$nin:u,$all:h,$size:l,$regex:a,$options:f,$elemMatch:d,$exists:y};const it={eq:p,ne:b,lt:w,lte:g,gt:$,gte:A,in:m,nin:M,all:j,size:E,regex:v,elemMatch:x,exists:_,and:F};const et=(i,e,s)=>t(Object.assign({},tt,i),Object.assign({},it,e),s);const st=t(tt,it);const nt=/[-/\\^$+?.()|[\]{}]/g;const rt=/\.?\*+\.?/g;const ot=/\*+/;const ct=/\./g;function ut(t,i,e){const s=e[0]==="*"||t[0]==="."&&t[t.length-1]==="."?"+":"*";const n=t.indexOf("**")===-1?"[^.]":".";const r=t.replace(ct,"\\$&").replace(ot,n+s);return i+t.length===e.length?`(?:${r})?`:r}function ht(t,i,e){if(t==="."&&(e[i-1]==="*"||e[i+1]==="*"))return t;return`\\${t}`}function lt(t){const i=t.map((t=>t.replace(nt,ht).replace(rt,ut)));const e=i.length>1?`(?:${i.join("|")})`:i[0];return new RegExp(`^${e}$`)}const at=t=>{let i;return e=>{if(typeof i==="undefined")i=t.every((t=>t.indexOf("*")===-1))?null:lt(t);return i===null?t.indexOf(e)!==-1:i.test(e)}};class Ability extends PureAbility{constructor(t=[],i={}){super(t,Object.assign({conditionsMatcher:st,fieldMatcher:at},i))}}function createMongoAbility(t=[],i={}){return new PureAbility(t,Object.assign({conditionsMatcher:st,fieldMatcher:at},i))}function isAbilityClass(t){return t.prototype!==void 0&&typeof t.prototype.possibleRulesFor==="function"}class ft{constructor(t){this.O=t}because(t){this.O.reason=t;return this}}class AbilityBuilder{constructor(t){this.rules=[];this.C=t;this.can=(t,i,e,s)=>this.R(t,i,e,s,false);this.cannot=(t,i,e,s)=>this.R(t,i,e,s,true);this.build=t=>isAbilityClass(this.C)?new this.C(this.rules,t):this.C(this.rules,t)}R(t,i,e,s,n){const r={action:t};if(n)r.inverted=n;if(i){r.subject=i;if(Array.isArray(e)||typeof e==="string")r.fields=e;else if(typeof e!=="undefined")r.conditions=e;if(typeof s!=="undefined")r.conditions=s}this.rules.push(r);return new ft(r)}}function defineAbility(t,i){const e=new AbilityBuilder(createMongoAbility);const s=t(e.can,e.cannot);if(s&&typeof s.then==="function")return s.then((()=>e.build(i)));return e.build(i)}const dt=t=>`Cannot execute "${t.action}" on "${t.subjectType}"`;const yt=function t(i){this.message=i};yt.prototype=Object.create(Error.prototype);class ForbiddenError extends yt{static setDefaultMessage(t){this.P=typeof t==="string"?()=>t:t}static from(t){return new this(t)}constructor(t){super("");this.ability=t;if(typeof Error.captureStackTrace==="function"){this.name="ForbiddenError";Error.captureStackTrace(this,this.constructor)}}setMessage(t){this.message=t;return this}throwUnlessCan(t,i,e){const s=this.unlessCan(t,i,e);if(s)throw s}unlessCan(t,i,e){const s=this.ability.relevantRuleFor(t,i,e);if(s&&!s.inverted)return;this.action=t;this.subject=i;this.subjectType=T(this.ability.detectSubjectType(i));this.field=e;const n=s?s.reason:"";this.message=this.message||n||this.constructor.P(this);return this}}ForbiddenError.P=dt;var pt=Object.freeze({__proto__:null});export{Ability,AbilityBuilder,ForbiddenError,PureAbility,et as buildMongoQueryMatcher,L as createAliasResolver,createMongoAbility,defineAbility,z as detectSubjectType,at as fieldPatternMatcher,dt as getDefaultErrorMessage,pt as hkt,st as mongoQueryMatcher,R as subject,O as wrapArray}; //# sourceMappingURL=index.mjs.map