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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | import { Forbidden } from "@feathersjs/errors"; import _isEmpty from "lodash/isEmpty"; import _pick from "lodash/pick"; import { mergeQuery, shouldSkip, pushSet, isMulti } from "@artesa/feathers-utils"; import getQueryFor from "../../utils/getQueryFor"; import hasRestrictingFields from "../../utils/hasRestrictingFields"; import hasRestrictingConditions from "../../utils/hasRestrictingConditions"; import { makeDefaultOptions, hide$select, setPersistedConfig, checkMulti, getAbility, throwUnlessCan } from "./authorize.hook.utils"; import { HookContext } from "@feathersjs/feathers"; import { AuthorizeHookOptions, GetQueryOptions } from "../../types"; const HOOKNAME = "authorize"; export default (options: AuthorizeHookOptions): ((context: HookContext) => Promise<HookContext>) => { options = makeDefaultOptions(options); return async (context: HookContext): Promise<HookContext> => { //TODO: make as isomorphic hook -> for vue-client if ( shouldSkip(HOOKNAME, context) || context.type !== "before" || !context.params ) { return context; } const { service, method, id, params } = context; const { getModelName } = options; const modelName = getModelName(context); if (!modelName) { return context; } params.ability = await getAbility(context, options); if (!params.ability) { // Interne Anfrage oder nicht authentifiziert -> Ignorieren return context; } const { ability } = params; const { subjectHelper, checkMultiActions } = options; if (checkMultiActions) { checkMulti(context, ability, modelName); } throwUnlessCan(ability, method, modelName, modelName); // if context is with multiple items, there's a change that we need to handle each iteam seperately if (isMulti(context)) { // if has conditions -> hide $select for after-hook, because if (hasRestrictingConditions(ability, "read", modelName)) { hide$select(context); } else { setPersistedConfig(context, "skipRestrictingRead.conditions", true); } // if has no restricting fields at all -> can skip _pick() in after-hook if (!hasRestrictingFields(ability, "read", modelName)) { setPersistedConfig(context, "skipRestrictingRead.fields", true); } } if (["get", "patch", "update", "remove"].includes(method) && id != null) { // single: get | patch | update | remove // get complete item for `throwUnlessCan`-check to be trustworthy // -> initial 'get' and 'remove' have no data at all // -> initial 'patch' maybe has just partial data // -> initial 'update' maybe has completely changed data, for what the check could pass but not for inital data const queryGet = Object.assign({}, params.query || {}); delete queryGet.$select; const paramsGet = Object.assign({}, params, { query: queryGet }); paramsGet.skipHooks = (params.skipHooks && params.skipHooks.slice()) || []; pushSet(paramsGet, "skipHooks", `${HOOKNAME}`, { unique: true }); const item = await service.get(id, paramsGet); throwUnlessCan(ability, method, subjectHelper(modelName, item), modelName); if (method === "get") { context.result = item; //pushSet(context, "params.skipHooks", "after"); return context; } // ensure that only allowed data gets changed if (["update", "patch"].includes(method)) { const fields = hasRestrictingFields(ability, method, subjectHelper(modelName, item)); if (!fields) { return context; } const data = _pick(context.data, fields); // if fields are not overlapping -> throw if (_isEmpty(data)) { throw new Forbidden("You're not allowed to make this request"); } //TODO: if some fields not match -> `actionOnForbiddenUpdate` if (method === "patch") { context.data = data; } else { // merge with inital data const itemPlain = await service._get(id); context.data = Object.assign({}, itemPlain, data); } } return context; } else if (method === "find" || (["patch", "remove"].includes(method) && id == null)) { // multi: find | patch | remove if (hasRestrictingConditions(ability, method, modelName)) { // TODO: if query and context.params.query differ -> seperate calls const options: GetQueryOptions = { skipFields: method === "find" }; const query = getQueryFor(ability, method, modelName, options); if (!_isEmpty(query)) { if (!context.params.query) { context.params.query = query; } else { context.params.query = mergeQuery(context.params.query, query, { defaultHandle: "intersect" }); } } } return context; } else if (method === "create") { // create: single | multi // we have all information we need (maybe we need populated data?) const data = (Array.isArray(context.data)) ? context.data : [context.data]; for (let i = 0; i < data.length; i++) { throwUnlessCan(ability, method, subjectHelper(modelName, data[i]), modelName); } return context; } return context; }; }; |