All files / lib/utils getConditionalQueryFor.ts

0% Statements 0/38
0% Branches 0/20
0% Functions 0/5
0% Lines 0/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89                                                                                                                                                                                 
import _isPlainObject from "lodash/isPlainObject";
import { Forbidden } from "@feathersjs/errors";
import { mergeQuery } from "@artesa/feathers-utils";
 
import { Ability, RawRuleFrom, AbilityTuple, Subject } from "@casl/ability";
import { Query } from "@feathersjs/feathers";
 
import { GetConditionalQueryOptions } from "../types";
 
const invertedMap = {
  "$gt": "$lte",
  "$gte": "$lt",
  "$lt": "$gte",
  "$lte": "$gt",
  "$in": "$nin",
  "$nin": "$in",
  "$ne": (prop: Record<string, unknown>): unknown => {
    return prop["$ne"];
  }
};
 
const invertedProp = (prop: Record<string, unknown>, name: string): Record<string, unknown>|string => {
  const map = invertedMap[name];
  if (typeof map === "string") {
    return { [map]: prop[name] };
  } else if(typeof map === "function") {
    return map(prop);
  }
};
 
const convertRuleToQuery = (rule: RawRuleFrom<AbilityTuple<string, Subject>, unknown>, options: GetConditionalQueryOptions): Query => {
  const { conditions, inverted } = rule;
  if (!conditions) {
    if (inverted && options?.actionOnForbidden) {
      options.actionOnForbidden();
    }
    return {} as Query;
  }
  if (inverted) {
    const newConditions = {} as Query;
    for (const prop in (conditions as Record<string, unknown>)) {
      if (_isPlainObject(conditions[prop])) {
        const obj = conditions[prop];
        for (const name in obj) {
          if (![
            "$gt",
            "$gte",
            "$lt",
            "$lte",
            "$in",
            "$nin",
            "$ne"
          ].includes(name)) {
            console.error(`CASL: not supported property: ${name}`);
            continue;
          }
          newConditions[prop] = invertedProp(obj, name);
        }
      } else {
        newConditions[prop] = { $ne: conditions[prop] };
      }
    }
 
    return newConditions;
  } else {
    return conditions as Query;
  }
};
 
export default (ability: Ability, method: string, subject: Subject, options?: GetConditionalQueryOptions): Query => {
  options = options || {};
  options.actionOnForbidden = options.actionOnForbidden || (() => {
    throw new Forbidden("You're not allowed to make this request");
  });
 
  const rules = ability.rulesFor(method, subject);
 
  let query = {};
 
  for (let i = 0; i < rules.length; i++) {
    const rule = rules[i];
 
    const currentQuery = convertRuleToQuery(rule, options);
    query = mergeQuery(query, currentQuery, { defaultHandle: "combine" });
  }
 
  return query;
};