feathers-casl
Version:
Add access control with CASL to your feathers application.
678 lines (677 loc) • 28.2 kB
JavaScript
import { isMulti, markHookForSkip, mergeArrays, mergeQuery, shouldSkip } from "@fratzinger/feathers-utils";
import _get from "lodash/get.js";
import _set from "lodash/set.js";
import { Forbidden } from "@feathersjs/errors";
import { detectSubjectType, subject } from "@casl/ability";
import { getDataIsArray, getResultIsArray, mutateResult } from "feathers-utils";
import _isPlainObject from "lodash/isPlainObject.js";
import { permittedFieldsOf, rulesToQuery } from "@casl/ability/extra";
import _isEmpty from "lodash/isEmpty.js";
import _isEqual from "lodash/isEqual.js";
import _cloneDeep from "lodash/cloneDeep.js";
import _pick from "lodash/pick.js";
import { Channel } from "@feathersjs/transport-commons";
//#region src/utils/getMethodName.ts
const getMethodName = (context, options) => {
if (options?.method) if (typeof options.method === "function") return options.method(context);
else return options.method;
return context.method;
};
//#endregion
//#region src/hooks/common.ts
const defaultOptions$3 = {
ability: void 0,
actionOnForbidden: void 0,
checkMultiActions: false,
checkAbilityForInternal: false,
modelName: (context) => {
return context.path;
},
notSkippable: false,
debug: false
};
const makeDefaultBaseOptions = () => {
return Object.assign({}, defaultOptions$3);
};
const checkCreatePerItem = (context, ability, modelName, options) => {
const method = getMethodName(context, options);
if (method !== "create" || !options.checkCreateForData) return context;
if (!(typeof options.checkCreateForData === "function" ? options.checkCreateForData(context) : true)) return context;
const { data: items } = getDataIsArray(context);
for (let i = 0, n = items.length; i < n; i++) throwUnlessCan(ability, method, subject(modelName, items[i]), modelName, options);
return context;
};
//#endregion
//#region src/utils/checkBasicPermission.ts
const defaultOptions$2 = {
checkCreateForData: false,
storeAbilityForAuthorize: false
};
const makeOptions$2 = (options) => {
options = options || {};
return Object.assign(makeDefaultBaseOptions(), defaultOptions$2, options);
};
const checkBasicPermissionUtil = async (context, _options) => {
let options = makeOptions$2(_options);
const method = getMethodName(context, options);
options = {
...options,
method
};
if (!options.modelName) return context;
const modelName = typeof options.modelName === "string" ? options.modelName : options.modelName(context);
if (!modelName) return context;
const ability = await getAbility$1(context, options);
if (!ability) return context;
if (options.checkMultiActions) checkMulti(context, ability, modelName, options);
throwUnlessCan(ability, method, modelName, modelName, options);
checkCreatePerItem(context, ability, modelName, options);
if (options.storeAbilityForAuthorize) setPersistedConfig(context, "ability", ability);
setPersistedConfig(context, "madeBasicCheck", true);
return context;
};
//#endregion
//#region src/utils/getFieldsForConditions.ts
const getFieldsForConditions = (ability, action, modelName) => {
const rules = ability.possibleRulesFor(action, modelName);
const allFields = [];
for (const rule of rules) {
if (!rule.conditions) continue;
Object.keys(rule.conditions).forEach((field) => {
if (!allFields.includes(field)) allFields.push(field);
});
}
return allFields;
};
//#endregion
//#region src/utils/checkCan.ts
const makeOptions$1 = (providedOptions) => {
return {
actionOnForbidden: () => {},
checkGeneral: true,
skipThrow: false,
useConditionalSelect: true,
...providedOptions
};
};
const checkCan = async (ability, id, method, modelName, service, providedOptions) => {
const options = makeOptions$1(providedOptions);
if (options.checkGeneral) {
if (!throwUnlessCan(ability, method, modelName, modelName, options)) return false;
}
let params;
if (options.useConditionalSelect) params = { query: { $select: getFieldsForConditions(ability, method, modelName) } };
return throwUnlessCan(ability, method, subject(modelName, await service[service._get ? "_get" : "get"](id, params)), modelName, options);
};
//#endregion
//#region src/utils/convertRuleToQuery.ts
const invertedMap = {
$gt: "$lte",
$gte: "$lt",
$lt: "$gte",
$lte: "$gt",
$in: "$nin",
$nin: "$in",
$ne: (prop) => {
return prop["$ne"];
}
};
const supportedOperators = Object.keys(invertedMap);
const invertedProp = (prop, name) => {
const map = invertedMap[name];
if (typeof map === "string") return { [map]: prop[name] };
else if (typeof map === "function") return map(prop);
};
const convertRuleToQuery = (rule, options) => {
const { conditions, inverted } = rule;
if (!conditions) {
if (inverted && options?.actionOnForbidden) options.actionOnForbidden();
return;
}
if (inverted) {
const newConditions = {};
for (const prop in conditions) if (_isPlainObject(conditions[prop])) {
const obj = conditions[prop];
for (const name in obj) {
if (!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;
};
//#endregion
//#region src/utils/couldHaveRestrictingFields.ts
function couldHaveRestrictingFields(ability, action, subjectType) {
return ability.possibleRulesFor(action, subjectType).some((rule) => {
return !!rule.fields;
});
}
//#endregion
//#region src/utils/getAvailableFields.ts
const getAvailableFields = (context, options) => {
return !options?.availableFields ? void 0 : typeof options.availableFields === "function" ? options.availableFields(context) : options.availableFields;
};
//#endregion
//#region src/utils/getModelName.ts
const getModelName = (modelName, context) => {
if (modelName === void 0) return context.path;
if (typeof modelName === "string") return modelName;
if (typeof modelName === "function") return modelName(context);
throw new Error("feathers-casl: 'modelName' is not a string or function");
};
//#endregion
//#region src/utils/hasRestrictingConditions.ts
const hasRestrictingConditions = (ability, action, modelName) => {
const rules = ability.possibleRulesFor(action, modelName);
return rules.length === 0 || rules.some((x) => !!x.conditions) ? rules : false;
};
//#endregion
//#region src/utils/getMinimalFields.ts
const getMinimalFields = (ability, action, subject, options) => {
if (options.checkCan && !ability.can(action, subject)) return [];
const subjectType = detectSubjectType(subject);
const rules = ability.possibleRulesFor(action, subjectType).filter((rule) => {
const { fields } = rule;
const matched = rule.matchesConditions(subject);
return fields && matched;
});
if (rules.length === 0) return options.availableFields || [];
let fields;
if (options.availableFields) fields = options.availableFields;
else {
fields = rules.find((x) => !x.inverted)?.fields;
if (!fields) return [];
}
rules.forEach((rule) => {
if (rule.inverted) fields = fields?.filter((x) => !rule.fields?.includes(x));
else fields = mergeArrays(fields, rule.fields, "intersect");
});
return fields;
};
//#endregion
//#region src/utils/hasRestrictingFields.ts
function areSameArray(arr1, arr2) {
if (arr1.length != arr2.length) return false;
const arr1test = arr1.slice().sort();
const arr2test = arr2.slice().sort();
return !arr1test.some((val, idx) => val !== arr2test[idx]);
}
const hasRestrictingFields = (ability, action, subject, options) => {
let fields;
if (typeof subject !== "string") fields = getMinimalFields(ability, action, subject, {
availableFields: options?.availableFields,
checkCan: false
});
else fields = permittedFieldsOf(ability, action, subject, { fieldsFrom: (rule) => {
return rule.fields || options?.availableFields || [];
} });
if (fields.length === 0 && !options?.availableFields) return false;
if (fields.length > 0) if (options?.availableFields === fields || options?.availableFields && areSameArray(fields, options?.availableFields)) return false;
else return fields;
return true;
};
//#endregion
//#region src/utils/simplifyQuery.ts
const simplifyQuery = (query, replaceAnd = true, replaceOr = true) => {
if (!query) return query;
if (!query.$and && !query.$or) return query;
let result = _cloneDeep(query);
if (result.$and && !result.$and.length) delete result.$and;
if (result.$or && !result.$or.length) delete result.$or;
if (result.$and) {
const $and = [];
result.$and.forEach((q) => {
q = simplifyQuery(q, true, true);
if ($and.some((x) => _isEqual(x, q))) return;
$and.push(q);
});
if (replaceAnd && $and.length === 1 && Object.keys(result).length === 1) result = $and[0];
else result.$and = $and;
}
if (result.$or) {
const $or = [];
result.$or.forEach((q) => {
q = simplifyQuery(q, true, true);
if ($or.some((x) => _isEqual(x, q))) return;
$or.push(q);
});
if (replaceOr && $or.length === 1 && Object.keys(result).length === 1) result = $or[0];
else result.$or = $or;
}
return result;
};
//#endregion
//#region src/utils/mergeQueryFromAbility.ts
const adaptersFor$not = [];
const adaptersFor$notAsArray = ["feathers-sequelize"];
const adaptersFor$nor = ["@feathersjs/memory", "@feathersjs/mongodb"];
const mergeQueryFromAbility = (app, ability, method, modelName, originalQuery, service, options) => {
if (!hasRestrictingConditions(ability, method, modelName)) return originalQuery;
const adapter = getAdapter(app, options);
let query;
if (adaptersFor$not.includes(adapter)) {
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule;
return rule.inverted ? { $not: conditions } : conditions;
});
query = simplifyQuery(query);
} else if (adaptersFor$notAsArray.includes(adapter)) {
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule;
return rule.inverted ? { $not: [conditions] } : conditions;
});
query = simplifyQuery(query);
} else if (adaptersFor$nor.includes(adapter)) {
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule;
return rule.inverted ? { $nor: [conditions] } : conditions;
});
query = simplifyQuery(query);
} else {
query = rulesToQuery(ability, method, modelName, (rule) => {
const { conditions } = rule;
return rule.inverted ? convertRuleToQuery(rule) : conditions;
});
query = simplifyQuery(query);
if (query?.$and) {
const { $and } = query;
delete query.$and;
$and.forEach((q) => {
query = mergeQuery(query, q, {
defaultHandle: "intersect",
useLogicalConjunction: true
});
});
}
}
if (_isEmpty(query)) return originalQuery;
if (!originalQuery) return query;
else return mergeQuery(originalQuery, query, {
defaultHandle: "intersect",
useLogicalConjunction: true
});
};
//#endregion
//#region src/hooks/authorize/authorize.hook.utils.ts
const HOOKNAME$1 = "authorize";
const makeOptions = (app, options) => {
options = options || {};
return Object.assign(makeDefaultBaseOptions(), defaultOptions$1, getAppOptions$1(app), options);
};
const defaultOptions$1 = {
adapter: void 0,
availableFields: (context) => {
const availableFields = context.service.options?.casl?.availableFields;
return getAvailableFields(context, { availableFields });
},
usePatchData: false,
useUpdateData: false
};
const makeDefaultOptions$1 = (options) => {
return Object.assign(makeDefaultBaseOptions(), defaultOptions$1, options);
};
const getAppOptions$1 = (app) => {
const caslOptions = app?.get("casl");
return caslOptions && caslOptions.authorizeHook ? caslOptions.authorizeHook : {};
};
const getAdapter = (app, options) => {
if (options.adapter) return options.adapter;
const caslAppOptions = app?.get("casl");
if (caslAppOptions?.defaultAdapter) return caslAppOptions.defaultAdapter;
return "@feathersjs/memory";
};
const getAbility$1 = (context, options) => {
const method = getMethodName(context, options);
if (context?.params?.ability) if (typeof context.params.ability === "function") {
const ability = context.params.ability(context);
return Promise.resolve(ability);
} else return Promise.resolve(context.params.ability);
const persistedAbility = getPersistedConfig(context, "ability");
if (persistedAbility) if (typeof persistedAbility === "function") {
const ability = persistedAbility(context);
return Promise.resolve(ability);
} else return Promise.resolve(persistedAbility);
if (!options?.checkAbilityForInternal && !context.params?.provider) return Promise.resolve(void 0);
if (options?.ability) if (typeof options.ability === "function") {
const ability = options.ability(context);
return Promise.resolve(ability);
} else return Promise.resolve(options.ability);
throw new Forbidden(`You're not allowed to ${method} on '${context.path}'`);
};
const throwUnlessCan = (ability, method, resource, modelName, options) => {
if (ability.cannot(method, resource)) {
if (options.actionOnForbidden) options.actionOnForbidden();
/* v8 ignore start */
if (options.debug) console.error("Feathers-CASL: throwUnlessCan - permission denied", method, modelName, resource, ability.relevantRuleFor(method, resource));
/* v8 ignore stop */
if (!options.skipThrow) throw new Forbidden(`You are not allowed to ${method} ${modelName}`);
return false;
}
return true;
};
const refetchItems = async (context, params) => {
if (!context.result) return;
const { result: items } = getResultIsArray(context);
if (!items) return;
const idField = context.service.options?.id;
const ids = items.map((item) => item[idField]);
params = Object.assign({}, params, { paginate: false });
markHookForSkip(HOOKNAME$1, "all", { params });
delete params.ability;
const query = Object.assign({}, params.query, { [idField]: { $in: ids } });
params = Object.assign({}, params, { query });
return await context.service.find(params);
};
const getConditionalSelect = ($select, ability, method, modelName) => {
if (!$select?.length) return;
const fields = getFieldsForConditions(ability, method, modelName);
if (!fields.length) return;
const fieldsToAdd = fields.filter((field) => !$select.includes(field));
if (!fieldsToAdd.length) return;
return [...$select, ...fieldsToAdd];
};
const checkMulti = (context, ability, modelName, options) => {
const method = getMethodName(context, options);
if (!isMulti(context)) return true;
if (method === "find" && ability.can(method, modelName) || ability.can(`${method}-multi`, modelName)) return true;
if (options?.actionOnForbidden) options.actionOnForbidden();
throw new Forbidden(`You're not allowed to multi-${method} ${modelName}`);
};
const setPersistedConfig = (context, key, val) => {
return _set(context, `params.casl.${key}`, val);
};
function getPersistedConfig(context, key) {
return _get(context, `params.casl.${key}`);
}
//#endregion
//#region src/hooks/authorize/authorize.hook.after.ts
const authorizeAfter = async (context, options) => {
if (shouldSkip("authorize", context, options) || !context.params) return context;
let { isArray, result: items } = getResultIsArray(context);
if (!items.length) return context;
options = makeOptions(context.app, options);
const modelName = getModelName(options.modelName, context);
if (!modelName) return context;
const skipCheckConditions = getPersistedConfig(context, "skipRestrictingRead.conditions");
const skipCheckFields = getPersistedConfig(context, "skipRestrictingRead.fields");
if (skipCheckConditions && skipCheckFields) return context;
const { params } = context;
params.ability = await getAbility$1(context, options);
if (!params.ability) return context;
const { ability } = params;
const hasRestrictingFieldsOptions = { availableFields: getAvailableFields(context, options) };
const getOrFind = isArray ? "find" : "get";
const $select = params.query?.$select;
const method = getMethodName(context, options);
if (method !== "remove") {
if (getConditionalSelect($select, ability, getOrFind, modelName)) {
const _items = await refetchItems(context);
if (_items) items = _items;
}
}
const pickFieldsForItem = (item) => {
if (!skipCheckConditions && !ability.can(getOrFind, subject(modelName, item))) return;
const restrictingFields = hasRestrictingFields(ability, getOrFind, subject(modelName, item), hasRestrictingFieldsOptions);
if (restrictingFields === true) return {};
else if (skipCheckFields || !restrictingFields && !$select) return item;
return _pick(item, restrictingFields && $select ? mergeArrays(restrictingFields, $select, "intersect") : restrictingFields || $select);
};
let newResult;
if (isArray) newResult = items.map(pickFieldsForItem).filter((x) => !!x);
else {
const single = pickFieldsForItem(items[0]);
if (method === "get" && _isEmpty(single)) {
if (options.actionOnForbidden) options.actionOnForbidden();
/* v8 ignore start */
if (options.debug) console.error("Feathers-CASL: authorizeAfter hook - all fields are restricted for this action", method, modelName, items[0]);
/* v8 ignore stop */
throw new Forbidden(`You're not allowed to ${method} ${modelName}`);
}
newResult = [single];
}
await mutateResult(context, (item) => item, { transform: () => newResult });
return context;
};
//#endregion
//#region src/hooks/checkBasicPermission.hook.ts
const HOOKNAME = "checkBasicPermission";
const checkBasicPermission = (_options) => {
return async (context) => {
if (!_options?.notSkippable && (shouldSkip(HOOKNAME, context) || context.type !== "before" || !context.params)) return context;
return await checkBasicPermissionUtil(context, _options);
};
};
//#endregion
//#region src/hooks/authorize/authorize.hook.before.ts
const authorizeBefore = async (context, options) => {
if (shouldSkip("authorize", context, options) || !context.params) return context;
const method = getMethodName(context, options);
if (!getPersistedConfig(context, "madeBasicCheck")) await checkBasicPermission({
notSkippable: true,
ability: options.ability,
actionOnForbidden: options.actionOnForbidden,
checkAbilityForInternal: options.checkAbilityForInternal,
checkCreateForData: true,
checkMultiActions: options.checkMultiActions,
modelName: options.modelName,
storeAbilityForAuthorize: true,
method
})(context);
if (!options.modelName) return context;
const modelName = typeof options.modelName === "string" ? options.modelName : options.modelName(context);
if (!modelName) return context;
const ability = await getAbility$1(context, options);
if (!ability) return context;
const multi = method === "find" || isMulti(context);
if (multi) {
if (!couldHaveRestrictingFields(ability, "find", modelName)) setPersistedConfig(context, "skipRestrictingRead.fields", true);
}
options = {
...options,
method
};
if (["find", "get"].includes(method) || multi && !hasRestrictingConditions(ability, "find", modelName)) setPersistedConfig(context, "skipRestrictingRead.conditions", true);
const { id } = context;
const availableFields = getAvailableFields(context, options);
if ([
"get",
"patch",
"update",
"remove"
].includes(method) && id != null) await handleSingle(context, ability, modelName, availableFields, options);
else if (method === "find" || ["patch", "remove"].includes(method) && id == null) await handleMulti(context, ability, modelName, availableFields, options);
else if (method === "create") checkCreatePerItem(context, ability, modelName, {
actionOnForbidden: options.actionOnForbidden,
checkCreateForData: true
});
return context;
};
const handleSingle = async (context, ability, modelName, availableFields, options) => {
const method = getMethodName(context, options);
options = {
...options,
method
};
const { params, service, id } = context;
_set(context, "params.query", mergeQueryFromAbility(context.app, ability, method, modelName, context.params?.query, context.service, options));
if (["update", "patch"].includes(method)) {
const queryGet = Object.assign({}, params.query || {});
if (queryGet.$select) delete queryGet.$select;
const paramsGet = Object.assign({}, params, { query: queryGet });
const getMethod = service._get ? "_get" : "get";
const restrictingFields = hasRestrictingFields(ability, method, subject(modelName, await service[getMethod](id, paramsGet).catch((err) => {
/* v8 ignore start */
if (options.debug) console.error("Feathers-CASL: authorizeBefore hook - error fetching item for single-item authorization check", getMethod, method, id, ability.relevantRuleFor(method, modelName), paramsGet.query, "query from casl", mergeQueryFromAbility(context.app, ability, method, modelName, {}, context.service, options));
/* v8 ignore stop */
throw err;
})), { availableFields });
if (restrictingFields && (restrictingFields === true || restrictingFields.length === 0)) {
if (options.actionOnForbidden) options.actionOnForbidden();
/* v8 ignore start */
if (options.debug) console.error("Feathers-CASL: authorizeBefore hook - all fields are restricted for this action", method, modelName, id);
/* v8 ignore stop */
throw new Forbidden("You're not allowed to make this request");
}
const data = !restrictingFields ? context.data : _pick(context.data, restrictingFields);
checkData(context, ability, modelName, data, options);
if (!restrictingFields) return context;
if (_isEmpty(data)) {
if (options.actionOnForbidden) options.actionOnForbidden();
/* v8 ignore start */
if (options.debug) console.error("Feathers-CASL: authorizeBefore hook - no fields are allowed to be changed for this action", method, modelName, id);
/* v8 ignore stop */
throw new Forbidden("You're not allowed to make this request");
}
if (method === "patch") context.data = data;
else {
const itemPlain = await service._get(id, {});
context.data = Object.assign({}, itemPlain, data);
}
}
return context;
};
const checkData = (context, ability, modelName, data, options) => {
const method = getMethodName(context, options);
if (method === "patch" && !options.usePatchData || method === "update" && !options.useUpdateData) return;
throwUnlessCan(ability, `${method}-data`, subject(modelName, data), modelName, options);
};
const handleMulti = async (context, ability, modelName, availableFields, options) => {
const method = getMethodName(context, options);
if (method === "patch") {
const fields = hasRestrictingFields(ability, method, modelName, { availableFields });
if (fields === true) {
if (options.actionOnForbidden) options.actionOnForbidden();
/* v8 ignore start */
if (options.debug) console.error("Feathers-CASL: authorizeBefore hook - all fields are restricted for multi-patch action", method, modelName);
/* v8 ignore stop */
throw new Forbidden("You're not allowed to make this request");
}
if (fields && fields.length > 0) context.data = _pick(context.data, fields);
}
_set(context, "params.query", mergeQueryFromAbility(context.app, ability, method, modelName, context.params?.query, context.service, options));
return context;
};
//#endregion
//#region src/hooks/authorize/authorize.hook.ts
const authorize = (_options) => async (context, next) => {
if (shouldSkip("authorize", context, _options) || !context.params || context.type === "error") return next ? await next() : context;
const options = makeOptions(context.app, _options);
if (next) {
await authorizeBefore(context, options);
await next();
await authorizeAfter(context, options);
return context;
}
return context.type === "before" ? await authorizeBefore(context, options) : await authorizeAfter(context, options);
};
//#endregion
//#region src/channels/channels.utils.ts
const makeChannelOptions = (app, options) => {
options = options || {};
return Object.assign({}, defaultOptions, getAppOptions(app), options);
};
const defaultOptions = {
activated: true,
channelOnError: ["authenticated"],
ability: (app, connection) => {
return connection.ability;
},
modelName: (context) => context.path,
restrictFields: true,
availableFields: (context) => {
const availableFields = context.service.options?.casl?.availableFields;
return getAvailableFields(context, { availableFields });
},
useActionName: "get"
};
const makeDefaultOptions = (options) => {
return Object.assign({}, defaultOptions, options);
};
const getAppOptions = (app) => {
const caslOptions = app?.get("casl");
return caslOptions && caslOptions.channels ? caslOptions.channels : {};
};
const getAbility = (app, data, connection, context, options) => {
if (options.ability) return typeof options.ability === "function" ? options.ability(app, connection, data, context) : options.ability;
else return connection.ability;
};
const eventNameMap = {
create: "created",
update: "updated",
patch: "patched",
remove: "removed"
};
const getEventName = (method) => eventNameMap[method];
//#endregion
//#region src/channels/getChannelsWithReadAbility.ts
const getChannelsWithReadAbility = (app, data, context, _options) => {
if (!_options?.channels && !app.channels.length) return;
const options = makeChannelOptions(app, _options);
const { channelOnError, activated } = options;
const modelName = getModelName(options.modelName, context);
if (!activated || !modelName) return !channelOnError ? new Channel() : app.channel(channelOnError);
let channels = options.channels || app.channel(app.channels);
if (!Array.isArray(channels)) channels = [channels];
const dataToTest = subject(modelName, data);
let method = "get";
if (typeof options.useActionName === "string") method = options.useActionName;
else {
const eventName = getEventName(context.method);
if (eventName && options.useActionName[eventName]) method = options.useActionName[eventName];
}
let result = [];
if (!options.restrictFields) result = channels.map((channel) => {
return channel.filter((conn) => {
const ability = getAbility(app, data, conn, context, options);
return !!ability && ability.can(method, dataToTest);
});
});
else {
const connectionsPerFields = [];
for (let i = 0, n = channels.length; i < n; i++) {
const { connections } = channels[i];
for (let j = 0, o = connections.length; j < o; j++) {
const connection = connections[j];
const { ability } = connection;
if (!ability || !ability.can(method, dataToTest)) continue;
const availableFields = getAvailableFields(context, options);
const fields = hasRestrictingFields(ability, method, dataToTest, { availableFields });
if (fields && (fields === true || fields.length === 0)) continue;
const connField = connectionsPerFields.find((x) => _isEqual(x.fields, fields));
if (connField) {
if (connField.connections.indexOf(connection) !== -1) continue;
connField.connections.push(connection);
} else connectionsPerFields.push({
connections: [connection],
fields
});
}
}
for (let i = 0, n = connectionsPerFields.length; i < n; i++) {
const { fields, connections } = connectionsPerFields[i];
const restrictedData = fields ? _pick(data, fields) : data;
if (!_isEmpty(restrictedData)) result.push(new Channel(connections, restrictedData));
}
}
return result.length === 1 ? result[0] : result;
};
//#endregion
//#region src/initialize.ts
const initialize = (options) => {
if (options?.version) throw new Error("You passed 'feathers-casl' to app.configure() without a function. You probably wanted to call app.configure(casl({}))!");
options = {
defaultAdapter: options?.defaultAdapter || "@feathersjs/memory",
authorizeHook: makeDefaultOptions$1(options?.authorizeHook),
channels: makeDefaultOptions(options?.channels)
};
return (app) => {
if (app.get("casl")) return;
app.set("casl", options);
};
};
//#endregion
export { authorize, checkBasicPermission, checkBasicPermissionUtil, checkCan, convertRuleToQuery, couldHaveRestrictingFields, initialize as feathersCasl, getAbility, getAvailableFields, getChannelsWithReadAbility, getEventName, getFieldsForConditions, getMinimalFields, getModelName, hasRestrictingConditions, hasRestrictingFields, makeChannelOptions, makeDefaultOptions, mergeQueryFromAbility, simplifyQuery };