All files / lib/channels getChannelsWithReadAbility.ts

84.62% Statements 44/52
67.57% Branches 25/37
66.67% Functions 2/3
82.98% Lines 39/47

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  1x 1x 1x 1x 1x   1x         1x 1x   1x               1x 46x 46x 46x   46x       46x   46x   40x   40x                     40x 40x 160x 160x 160x   104x   56x           56x   56x     56x 56x 8x       8x   48x           40x 40x 48x 48x     48x 48x     40x        
import { HookContext, Application } from "@feathersjs/feathers";
import _isEqual from "lodash/isEqual";
import _pick from "lodash/pick";
import _isEmpty from "lodash/isEmpty";
import "@feathersjs/transport-commons";
import { subject } from "@casl/ability";
 
import { 
  makeOptions, 
  getAbility 
} from "./channels.utils";
 
import getModelName from "../utils/getModelName";
import hasRestrictingFields from "../utils/hasRestrictingFields";
 
import { Channel, RealTimeConnection } from "@feathersjs/transport-commons/lib/channels/channel/base";
import { ChannelOptions } from "../types";
 
interface ConnectionsPerField {
  fields: false | string[], 
  connections: RealTimeConnection[]
}
 
export default (app: Application, data: Record<string, unknown>, context: HookContext, options?: Partial<ChannelOptions>): Channel|Channel[] => {
  options = makeOptions(app, options);
  const { channelOnError, activated } = options;
  const modelName = getModelName(options.modelName, context);
 
  Iif (!activated || !modelName) {
    return (!channelOnError) ? new Channel() : app.channel(channelOnError);
  }
 
  const { channels } = app;
  //return app.channel(channels);
  const allConnections = app.channel(channels).connections;
 
  const dataToTest = subject(modelName, data);
 
  Iif (!options.restrictFields) {
    // return all fields for allowed 
    let connections = allConnections
      .filter(connection => {
        const ability = getAbility(app, data, connection, context, options);
        return ability && ability.can("get", dataToTest);
      });
    connections = [...new Set(connections)];
    return new Channel(connections, data);
  } else {
    // filter by restricted Fields
    const connectionsPerFields: ConnectionsPerField[] = [];
    for (let i = 0, n = allConnections.length; i < n; i++) {
      const connection = allConnections[i];
      const { ability } = connection;
      if (!ability || !ability.can("get", dataToTest)) {
        // connection cannot read item -> don't send data
        continue; 
      }
      const availableFields = (!options?.availableFields)
        ? undefined
        : (typeof options.availableFields === "function")
          ? options.availableFields(context)
          : options.availableFields;
 
      const fields = hasRestrictingFields(ability, "get", dataToTest, { availableFields });
      // if fields is true or fields is empty array -> full restriction
      Iif (fields && (fields === true || fields.length === 0)) {
        continue;
      }
      const connField = connectionsPerFields.find(x => _isEqual(x.fields, fields));
      if (connField) {
        Iif (connField.connections.indexOf(connection) !== -1) {
          // connection is already in array -> skip
          continue; 
        }
        connField.connections.push(connection);
      } else {
        connectionsPerFields.push({
          connections: [connection],
          fields: fields as string[] | false
        });
      }
    }
    const channels: Channel[] = [];
    for (let i = 0, n = connectionsPerFields.length; i < n; i++) {
      const { fields, connections } = connectionsPerFields[i];
      const restrictedData = (fields) 
        ? _pick(data, fields)
        : data;
      Eif (!_isEmpty(restrictedData)) {
        channels.push(new Channel(connections, restrictedData));
      }
    }
    return channels.length === 1 
      ? channels[0]
      : channels;
  }
};