@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.27 kB
JavaScript
;var t=require("@ucast/mongo2js");function e(t){return Array.isArray(t)?t:[t]}const i="__caslSubjectType__";function s(t,e){if(e)if(!Object.hasOwn(e,i))Object.defineProperty(e,i,{value:t});else if(t!==e[i])throw new Error(`Trying to cast object to subject type ${t} but previously it was casted to ${e[i]}`);return e}const r=t=>{const e=typeof t;return e==="string"||e==="function"};const n=t=>t.modelName||t.name;function o(t){return typeof t==="string"?t:n(t)}function c(t){if(Object.hasOwn(t,i))return t[i];return n(t.constructor)}const u={function:t=>t.constructor,string:c};function h(t,i,s){let r=e(i);let n=0;while(n<r.length){const e=r[n++];if(Object.hasOwn(t,e))r=s(r,t[e])}return r}function l(t,e){if(typeof e==="string"&&t.indexOf(e)!==-1)return e;for(let i=0;i<e.length;i++)if(t.indexOf(e[i])!==-1)return e[i];return null}const a=(t,e)=>t.concat(e);function f(t,e){if(e in t)throw new Error(`Cannot use "${e}" as an alias because it's reserved action.`);const i=Object.keys(t);const s=(t,i)=>{const s=l(t,i);if(s)throw new Error(`Detected cycle ${s} -> ${t.join(", ")}`);const r=typeof i==="string"&&i===e||t.indexOf(e)!==-1||Array.isArray(i)&&i.indexOf(e)!==-1;if(r)throw new Error(`Cannot make an alias to "${e}" because this is reserved action`);return t.concat(i)};for(let e=0;e<i.length;e++)h(t,i[e],s)}function d(t,e){if(!e||e.skipValidate!==false)f(t,e&&e.anyAction||"manage");return e=>h(t,e,a)}function p(t,e,i){for(let s=i;s<e.length;s++)t.push(e[s])}function y(t,e){if(!t||!t.length)return e||[];if(!e||!e.length)return t||[];let i=0;let s=0;const r=[];while(i<t.length&&s<e.length)if(t[i].priority<e[s].priority){r.push(t[i]);i++}else{r.push(e[s]);s++}p(r,t,i);p(r,e,s);return r}function b(t,e,i){let s=t.get(e);if(!s){s=i();t.set(e,s)}return s}const w=t=>t;function g(t,e){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&&!e.fieldMatcher)throw new Error('You need to pass "fieldMatcher" option in order to restrict access by fields');if(t.conditions&&!e.conditionsMatcher)throw new Error('You need to pass "conditionsMatcher" option in order to restrict access by conditions')}class x{constructor(t,i,s=0){g(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?e(t.fields):void 0;this.priority=s;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||r(t))return!this.inverted;const e=this.i();return e(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 $(t,e){const i={value:t,prev:e,next:null};if(e)e.next=i;return i}function A(t){if(t.next)t.next.prev=t.prev;if(t.prev)t.prev.next=t.next;t.next=t.prev=null}const M=t=>({value:t.value,prev:t.prev,next:t.next});const m=()=>({rules:[],merged:false});const j=()=>new Map;class v{constructor(t=[],e={}){this.h=false;this.l=new Map;this.p={conditionsMatcher:e.conditionsMatcher,fieldMatcher:e.fieldMatcher,resolveAction:e.resolveAction||w};this.$=e.anyAction||"manage";this.A=e.anySubjectType||"all";this.M=t;this.m=!!e.detectSubjectType;this.j=e.detectSubjectType||c;this.v(t)}get rules(){return this.M}detectSubjectType(t){if(r(t))return t;if(!t)return this.A;return this.j(t)}update(t){const e={rules:t,ability:this,target:this};this._("update",e);this.M=t;this.v(t);this._("updated",e);return this}v(t){const i=new Map;let s;for(let r=t.length-1;r>=0;r--){const n=t.length-r-1;const o=new x(t[r],this.p,n);const c=e(o.action);const u=e(o.subject||this.A);if(!this.h&&o.fields)this.h=true;for(let t=0;t<u.length;t++){const e=b(i,u[t],j);if(s===void 0)s=typeof u[t];if(typeof u[t]!==s&&s!=="mixed")s="mixed";for(let t=0;t<c.length;t++)b(e,c[t],m).rules.push(o)}}this.l=i;if(s!=="mixed"&&!this.m){const t=u[s]||u.string;this.j=t}}possibleRulesFor(t,e=this.A){if(!r(e))throw new Error('"possibleRulesFor" accepts only subject types (i.e., string or class) as the 2nd parameter');const i=b(this.l,e,j);const s=b(i,t,m);if(s.merged)return s.rules;const n=t!==this.$&&i.has(this.$)?i.get(this.$).rules:void 0;let o=y(s.rules,n);if(e!==this.A)o=y(o,this.possibleRulesFor(t,this.A));s.rules=o;s.merged=true;return o}rulesFor(t,e,i){const s=this.possibleRulesFor(t,e);if(i&&typeof i!=="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(i)))}actionsFor(t){if(!r(t))throw new Error('"actionsFor" accepts only subject types (i.e., string or class) as a parameter');const e=new Set;const i=this.l.get(t);if(i)Array.from(i.keys()).forEach((t=>e.add(t)));const s=t!==this.A?this.l.get(this.A):void 0;if(s)Array.from(s.keys()).forEach((t=>e.add(t)));return Array.from(e)}on(t,e){this.F=this.F||new Map;const i=this.F;const s=i.get(t)||null;const r=$(e,s);i.set(t,r);return()=>{const e=i.get(t);if(!r.next&&!r.prev&&e===r)i.delete(t);else if(r===e)i.set(t,r.prev);A(r)}}_(t,e){if(!this.F)return;let i=this.F.get(t)||null;while(i!==null){const t=i.prev?M(i.prev):null;i.value(e);i=t}}}class PureAbility extends v{can(t,e,i){const s=this.relevantRuleFor(t,e,i);return!!s&&!s.inverted}relevantRuleFor(t,e,i){const s=this.detectSubjectType(e);const r=this.rulesFor(t,s,i);for(let t=0,i=r.length;t<i;t++)if(r[t].matchesConditions(e))return r[t];return null}cannot(t,e,i){return!this.can(t,e,i)}}const E={$eq:t.$eq,$ne:t.$ne,$lt:t.$lt,$lte:t.$lte,$gt:t.$gt,$gte:t.$gte,$in:t.$in,$nin:t.$nin,$all:t.$all,$size:t.$size,$regex:t.$regex,$options:t.$options,$elemMatch:t.$elemMatch,$exists:t.$exists};const _={eq:t.eq,ne:t.ne,lt:t.lt,lte:t.lte,gt:t.gt,gte:t.gte,in:t.within,nin:t.nin,all:t.all,size:t.size,regex:t.regex,elemMatch:t.elemMatch,exists:t.exists,and:t.and};const F=(e,i,s)=>t.createFactory(Object.assign({},E,e),Object.assign({},_,i),s);const O=t.createFactory(E,_);const C=/[-/\\^$+?.()|[\]{}]/g;const R=/\.?\*+\.?/g;const P=/\*+/;const S=/\./g;function T(t,e,i){const s=i[0]==="*"||t[0]==="."&&t[t.length-1]==="."?"+":"*";const r=t.indexOf("**")===-1?"[^.]":".";const n=t.replace(S,"\\$&").replace(P,r+s);return e+t.length===i.length?`(?:${n})?`:n}function q(t,e,i){if(t==="."&&(i[e-1]==="*"||i[e+1]==="*"))return t;return`\\${t}`}function z(t){const e=t.map((t=>t.replace(C,q).replace(R,T)));const i=e.length>1?`(?:${e.join("|")})`:e[0];return new RegExp(`^${i}$`)}const B=t=>{let e;return i=>{if(typeof e==="undefined")e=t.every((t=>t.indexOf("*")===-1))?null:z(t);return e===null?t.indexOf(i)!==-1:e.test(i)}};class Ability extends PureAbility{constructor(t=[],e={}){super(t,Object.assign({conditionsMatcher:O,fieldMatcher:B},e))}}function createMongoAbility(t=[],e={}){return new PureAbility(t,Object.assign({conditionsMatcher:O,fieldMatcher:B},e))}function isAbilityClass(t){return t.prototype!==void 0&&typeof t.prototype.possibleRulesFor==="function"}class D{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,e,i,s)=>this.R(t,e,i,s,false);this.cannot=(t,e,i,s)=>this.R(t,e,i,s,true);this.build=t=>isAbilityClass(this.C)?new this.C(this.rules,t):this.C(this.rules,t)}R(t,e,i,s,r){const n={action:t};if(r)n.inverted=r;if(e){n.subject=e;if(Array.isArray(i)||typeof i==="string")n.fields=i;else if(typeof i!=="undefined")n.conditions=i;if(typeof s!=="undefined")n.conditions=s}this.rules.push(n);return new D(n)}}function defineAbility(t,e){const i=new AbilityBuilder(createMongoAbility);const s=t(i.can,i.cannot);if(s&&typeof s.then==="function")return s.then((()=>i.build(e)));return i.build(e)}const Y=t=>`Cannot execute "${t.action}" on "${t.subjectType}"`;const k=function t(e){this.message=e};k.prototype=Object.create(Error.prototype);class ForbiddenError extends k{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,e,i){const s=this.unlessCan(t,e,i);if(s)throw s}unlessCan(t,e,i){const s=this.ability.relevantRuleFor(t,e,i);if(s&&!s.inverted)return;this.action=t;this.subject=e;this.subjectType=o(this.ability.detectSubjectType(e));this.field=i;const r=s?s.reason:"";this.message=this.message||r||this.constructor.P(this);return this}}ForbiddenError.P=Y;var L=Object.freeze({__proto__:null});exports.Ability=Ability;exports.AbilityBuilder=AbilityBuilder;exports.ForbiddenError=ForbiddenError;exports.PureAbility=PureAbility;exports.buildMongoQueryMatcher=F;exports.createAliasResolver=d;exports.createMongoAbility=createMongoAbility;exports.defineAbility=defineAbility;exports.detectSubjectType=c;exports.fieldPatternMatcher=B;exports.getDefaultErrorMessage=Y;exports.hkt=L;exports.mongoQueryMatcher=O;exports.subject=s;exports.wrapArray=e;
//# sourceMappingURL=index.js.map