UNPKG

feathers-casl

Version:

Add access control with CASL to your feathers application.

678 lines (677 loc) 28.2 kB
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 };