All files / lib/utils getConditionalQueryFor.ts

90.91% Statements 40/44
62.5% Branches 15/24
80% Functions 4/5
90.7% Lines 39/43

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 90 91 92 93 94 95 96 97 98 99 1001x 1x 1x             1x               6x       1x   1x       42x 42x 36x 6x 6x       1x       128x 128x 59x     59x   69x 49x 49x 49x 42x 42x 42x       42x     7x       49x   20x       1x           77x 77x       77x   77x   77x 128x   128x 128x             77x     1x  
import _isPlainObject from "lodash/isPlainObject";
import { Forbidden } from "@feathersjs/errors";
import { mergeQuery } from "feathers-utils";
 
import { AnyAbility, 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 supportedOperators = Object.keys(invertedMap);
 
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 Eif(typeof map === "function") {
    return map(prop);
  }
};
 
const convertRuleToQuery = (
  rule: RawRuleFrom<AbilityTuple<string, Subject>, unknown>, 
  options: GetConditionalQueryOptions): Query => 
{
  const { conditions, inverted } = rule;
  if (!conditions) {
    Iif (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) {
          Iif (!supportedOperators.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;
  }
};
 
const getConditionalQueryFor = (
  ability: AnyAbility, 
  method: string, 
  subjectType: string, 
  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, subjectType);
 
  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;
};
 
export default getConditionalQueryFor;