@strapi/utils 5.51.2 → 5.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/content-types.d.ts.map +1 -1
- package/dist/content-types.js +39 -2
- package/dist/content-types.js.map +1 -1
- package/dist/content-types.mjs +39 -2
- package/dist/content-types.mjs.map +1 -1
- package/dist/convert-query-params.js +4 -4
- package/dist/convert-query-params.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/parse-type.d.ts +13 -0
- package/dist/parse-type.d.ts.map +1 -1
- package/dist/parse-type.js +42 -13
- package/dist/parse-type.js.map +1 -1
- package/dist/parse-type.mjs +39 -13
- package/dist/parse-type.mjs.map +1 -1
- package/dist/sanitize/sanitizers.d.ts.map +1 -1
- package/dist/sanitize/sanitizers.js +5 -3
- package/dist/sanitize/sanitizers.js.map +1 -1
- package/dist/sanitize/sanitizers.mjs +5 -3
- package/dist/sanitize/sanitizers.mjs.map +1 -1
- package/dist/sanitize/visitors/remove-restricted-relations.d.ts.map +1 -1
- package/dist/sanitize/visitors/remove-restricted-relations.js +128 -86
- package/dist/sanitize/visitors/remove-restricted-relations.js.map +1 -1
- package/dist/sanitize/visitors/remove-restricted-relations.mjs +128 -86
- package/dist/sanitize/visitors/remove-restricted-relations.mjs.map +1 -1
- package/dist/traverse-entity.d.ts.map +1 -1
- package/dist/traverse-entity.js +9 -1
- package/dist/traverse-entity.js.map +1 -1
- package/dist/traverse-entity.mjs +9 -1
- package/dist/traverse-entity.mjs.map +1 -1
- package/dist/validate/validators.js +12 -19
- package/dist/validate/validators.js.map +1 -1
- package/dist/validate/validators.mjs +13 -20
- package/dist/validate/validators.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -25,9 +25,11 @@ const defaultSanitizeOutput = async (ctx, entity)=>{
|
|
|
25
25
|
if (!ctx.schema) {
|
|
26
26
|
throw new Error('Missing schema in defaultSanitizeOutput');
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Named parameters rather than a rest-spread: this runs once per key, per node, per
|
|
29
|
+
// entity, and `(...args)` allocated a fresh array on every call.
|
|
30
|
+
return traverseEntity((visitorOptions, visitorUtils)=>{
|
|
31
|
+
visitor$1(visitorOptions, visitorUtils);
|
|
32
|
+
visitor(visitorOptions, visitorUtils);
|
|
31
33
|
}, ctx, entity);
|
|
32
34
|
};
|
|
33
35
|
const defaultSanitizeFilters = curry((ctx, filters)=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitizers.mjs","sources":["../../src/sanitize/sanitizers.ts"],"sourcesContent":["import { curry, isEmpty, isNil, isArray, isPlainObject } from 'lodash/fp';\n\nimport { pipe as pipeAsync } from '../async';\nimport traverseEntity from '../traverse-entity';\nimport { isScalarAttribute, constants } from '../content-types';\n\nimport {\n traverseQueryFilters,\n traverseQuerySort,\n traverseQueryPopulate,\n traverseQueryFields,\n} from '../traverse';\n\nimport {\n removePassword,\n removePrivate,\n removeDynamicZones,\n removeMorphToRelations,\n expandWildcardPopulate,\n} from './visitors';\nimport { isOperator } from '../operators';\n\nimport type { Model, Data } from '../types';\nimport type { Parent } from '../traverse/factory';\n\ninterface Context {\n schema: Model;\n getModel: (model: string) => Model;\n parent?: Parent;\n}\n\nconst { ID_ATTRIBUTE, DOC_ID_ATTRIBUTE } = constants;\n\nconst sanitizePasswords = (ctx: Context) => async (entity: Data) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in sanitizePasswords');\n }\n\n return traverseEntity(removePassword, ctx, entity);\n};\n\nconst defaultSanitizeOutput = async (ctx: Context, entity: Data) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeOutput');\n }\n\n return traverseEntity(\n (...args) => {\n removePassword(...args);\n removePrivate(...args);\n },\n ctx,\n entity\n );\n};\n\nconst defaultSanitizeFilters = curry((ctx: Context, filters: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeFilters');\n }\n\n return pipeAsync(\n // Remove keys that are not attributes or valid operators\n traverseQueryFilters(({ key, attribute }, { remove }) => {\n const isAttribute = !!attribute;\n\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not checking it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (!isAttribute && !isOperator(key)) {\n remove(key);\n }\n }, ctx),\n // Remove dynamic zones from filters\n traverseQueryFilters(removeDynamicZones, ctx),\n // Remove morpTo relations from filters\n traverseQueryFilters(removeMorphToRelations, ctx),\n // Remove passwords from filters\n traverseQueryFilters(removePassword, ctx),\n // Remove private from filters\n traverseQueryFilters(removePrivate, ctx),\n // Remove empty plain objects and empty arrays. Do not use lodash isObject+isEmpty: built-ins with no\n // enumerable keys (Date, RegExp, boxed primitives, etc.) are \"empty\" and would wrongly drop valid operands.\n traverseQueryFilters(({ key, value }, { remove }) => {\n const isEmptyPlainObject = isPlainObject(value) && isEmpty(value);\n const isEmptyArrayOperand = isArray(value) && isEmpty(value);\n if (isEmptyPlainObject || isEmptyArrayOperand) {\n remove(key);\n }\n }, ctx)\n )(filters);\n});\n\nconst defaultSanitizeSort = curry((ctx: Context, sort: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeSort');\n }\n\n return pipeAsync(\n // Remove non attribute keys\n traverseQuerySort(({ key, attribute }, { remove }) => {\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not checking it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (!attribute) {\n remove(key);\n }\n }, ctx),\n // Remove dynamic zones from sort\n traverseQuerySort(removeDynamicZones, ctx),\n // Remove morpTo relations from sort\n traverseQuerySort(removeMorphToRelations, ctx),\n // Remove private from sort\n traverseQuerySort(removePrivate, ctx),\n // Remove passwords from filters\n traverseQuerySort(removePassword, ctx),\n // Remove keys for empty non-scalar values\n traverseQuerySort(({ key, attribute, value }, { remove }) => {\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not removing it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (!isScalarAttribute(attribute) && isEmpty(value)) {\n remove(key);\n }\n }, ctx)\n )(sort);\n});\n\nconst defaultSanitizeFields = curry((ctx: Context, fields: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeFields');\n }\n\n return pipeAsync(\n // Only keep scalar attributes\n traverseQueryFields(({ key, attribute }, { remove }) => {\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not checking it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (isNil(attribute) || !isScalarAttribute(attribute)) {\n remove(key);\n }\n }, ctx),\n // Remove private fields\n traverseQueryFields(removePrivate, ctx),\n // Remove password fields\n traverseQueryFields(removePassword, ctx),\n // Remove nil values from fields array\n (value) => (isArray(value) ? value.filter((field) => !isNil(field)) : value)\n )(fields);\n});\n\nconst defaultSanitizePopulate = curry((ctx: Context, populate: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizePopulate');\n }\n\n return pipeAsync(\n traverseQueryPopulate(expandWildcardPopulate, ctx),\n traverseQueryPopulate(async ({ key, value, schema, attribute, getModel, path }, { set }) => {\n if (attribute) {\n return;\n }\n\n const parent = { key, path, schema, attribute } satisfies Parent;\n\n if (key === 'sort') {\n set(key, await defaultSanitizeSort({ schema, getModel, parent }, value));\n }\n\n if (key === 'filters') {\n set(key, await defaultSanitizeFilters({ schema, getModel, parent }, value));\n }\n\n if (key === 'fields') {\n set(key, await defaultSanitizeFields({ schema, getModel, parent }, value));\n }\n\n if (key === 'populate') {\n set(key, await defaultSanitizePopulate({ schema, getModel, parent }, value));\n }\n }, ctx),\n // Remove private fields\n traverseQueryPopulate(removePrivate, ctx)\n )(populate);\n});\n\nexport {\n sanitizePasswords,\n defaultSanitizeOutput,\n defaultSanitizeFilters,\n defaultSanitizeSort,\n defaultSanitizeFields,\n defaultSanitizePopulate,\n};\n"],"names":["ID_ATTRIBUTE","DOC_ID_ATTRIBUTE","constants","sanitizePasswords","ctx","entity","schema","Error","traverseEntity","removePassword","defaultSanitizeOutput","args","removePrivate","defaultSanitizeFilters","curry","filters","pipeAsync","traverseQueryFilters","key","attribute","remove","isAttribute","includes","isOperator","removeDynamicZones","removeMorphToRelations","value","isEmptyPlainObject","isPlainObject","isEmpty","isEmptyArrayOperand","isArray","defaultSanitizeSort","sort","traverseQuerySort","isScalarAttribute","defaultSanitizeFields","fields","traverseQueryFields","isNil","filter","field","defaultSanitizePopulate","populate","traverseQueryPopulate","expandWildcardPopulate","getModel","path","set","parent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sanitizers.mjs","sources":["../../src/sanitize/sanitizers.ts"],"sourcesContent":["import { curry, isEmpty, isNil, isArray, isPlainObject } from 'lodash/fp';\n\nimport { pipe as pipeAsync } from '../async';\nimport traverseEntity from '../traverse-entity';\nimport { isScalarAttribute, constants } from '../content-types';\n\nimport {\n traverseQueryFilters,\n traverseQuerySort,\n traverseQueryPopulate,\n traverseQueryFields,\n} from '../traverse';\n\nimport {\n removePassword,\n removePrivate,\n removeDynamicZones,\n removeMorphToRelations,\n expandWildcardPopulate,\n} from './visitors';\nimport { isOperator } from '../operators';\n\nimport type { Model, Data } from '../types';\nimport type { Parent } from '../traverse/factory';\n\ninterface Context {\n schema: Model;\n getModel: (model: string) => Model;\n parent?: Parent;\n}\n\nconst { ID_ATTRIBUTE, DOC_ID_ATTRIBUTE } = constants;\n\nconst sanitizePasswords = (ctx: Context) => async (entity: Data) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in sanitizePasswords');\n }\n\n return traverseEntity(removePassword, ctx, entity);\n};\n\nconst defaultSanitizeOutput = async (ctx: Context, entity: Data) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeOutput');\n }\n\n // Named parameters rather than a rest-spread: this runs once per key, per node, per\n // entity, and `(...args)` allocated a fresh array on every call.\n return traverseEntity(\n (visitorOptions, visitorUtils) => {\n removePassword(visitorOptions, visitorUtils);\n removePrivate(visitorOptions, visitorUtils);\n },\n ctx,\n entity\n );\n};\n\nconst defaultSanitizeFilters = curry((ctx: Context, filters: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeFilters');\n }\n\n return pipeAsync(\n // Remove keys that are not attributes or valid operators\n traverseQueryFilters(({ key, attribute }, { remove }) => {\n const isAttribute = !!attribute;\n\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not checking it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (!isAttribute && !isOperator(key)) {\n remove(key);\n }\n }, ctx),\n // Remove dynamic zones from filters\n traverseQueryFilters(removeDynamicZones, ctx),\n // Remove morpTo relations from filters\n traverseQueryFilters(removeMorphToRelations, ctx),\n // Remove passwords from filters\n traverseQueryFilters(removePassword, ctx),\n // Remove private from filters\n traverseQueryFilters(removePrivate, ctx),\n // Remove empty plain objects and empty arrays. Do not use lodash isObject+isEmpty: built-ins with no\n // enumerable keys (Date, RegExp, boxed primitives, etc.) are \"empty\" and would wrongly drop valid operands.\n traverseQueryFilters(({ key, value }, { remove }) => {\n const isEmptyPlainObject = isPlainObject(value) && isEmpty(value);\n const isEmptyArrayOperand = isArray(value) && isEmpty(value);\n if (isEmptyPlainObject || isEmptyArrayOperand) {\n remove(key);\n }\n }, ctx)\n )(filters);\n});\n\nconst defaultSanitizeSort = curry((ctx: Context, sort: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeSort');\n }\n\n return pipeAsync(\n // Remove non attribute keys\n traverseQuerySort(({ key, attribute }, { remove }) => {\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not checking it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (!attribute) {\n remove(key);\n }\n }, ctx),\n // Remove dynamic zones from sort\n traverseQuerySort(removeDynamicZones, ctx),\n // Remove morpTo relations from sort\n traverseQuerySort(removeMorphToRelations, ctx),\n // Remove private from sort\n traverseQuerySort(removePrivate, ctx),\n // Remove passwords from filters\n traverseQuerySort(removePassword, ctx),\n // Remove keys for empty non-scalar values\n traverseQuerySort(({ key, attribute, value }, { remove }) => {\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not removing it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (!isScalarAttribute(attribute) && isEmpty(value)) {\n remove(key);\n }\n }, ctx)\n )(sort);\n});\n\nconst defaultSanitizeFields = curry((ctx: Context, fields: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizeFields');\n }\n\n return pipeAsync(\n // Only keep scalar attributes\n traverseQueryFields(({ key, attribute }, { remove }) => {\n // ID is not an attribute per se, so we need to make\n // an extra check to ensure we're not checking it\n if ([ID_ATTRIBUTE, DOC_ID_ATTRIBUTE].includes(key)) {\n return;\n }\n\n if (isNil(attribute) || !isScalarAttribute(attribute)) {\n remove(key);\n }\n }, ctx),\n // Remove private fields\n traverseQueryFields(removePrivate, ctx),\n // Remove password fields\n traverseQueryFields(removePassword, ctx),\n // Remove nil values from fields array\n (value) => (isArray(value) ? value.filter((field) => !isNil(field)) : value)\n )(fields);\n});\n\nconst defaultSanitizePopulate = curry((ctx: Context, populate: unknown) => {\n if (!ctx.schema) {\n throw new Error('Missing schema in defaultSanitizePopulate');\n }\n\n return pipeAsync(\n traverseQueryPopulate(expandWildcardPopulate, ctx),\n traverseQueryPopulate(async ({ key, value, schema, attribute, getModel, path }, { set }) => {\n if (attribute) {\n return;\n }\n\n const parent = { key, path, schema, attribute } satisfies Parent;\n\n if (key === 'sort') {\n set(key, await defaultSanitizeSort({ schema, getModel, parent }, value));\n }\n\n if (key === 'filters') {\n set(key, await defaultSanitizeFilters({ schema, getModel, parent }, value));\n }\n\n if (key === 'fields') {\n set(key, await defaultSanitizeFields({ schema, getModel, parent }, value));\n }\n\n if (key === 'populate') {\n set(key, await defaultSanitizePopulate({ schema, getModel, parent }, value));\n }\n }, ctx),\n // Remove private fields\n traverseQueryPopulate(removePrivate, ctx)\n )(populate);\n});\n\nexport {\n sanitizePasswords,\n defaultSanitizeOutput,\n defaultSanitizeFilters,\n defaultSanitizeSort,\n defaultSanitizeFields,\n defaultSanitizePopulate,\n};\n"],"names":["ID_ATTRIBUTE","DOC_ID_ATTRIBUTE","constants","sanitizePasswords","ctx","entity","schema","Error","traverseEntity","removePassword","defaultSanitizeOutput","visitorOptions","visitorUtils","removePrivate","defaultSanitizeFilters","curry","filters","pipeAsync","traverseQueryFilters","key","attribute","remove","isAttribute","includes","isOperator","removeDynamicZones","removeMorphToRelations","value","isEmptyPlainObject","isPlainObject","isEmpty","isEmptyArrayOperand","isArray","defaultSanitizeSort","sort","traverseQuerySort","isScalarAttribute","defaultSanitizeFields","fields","traverseQueryFields","isNil","filter","field","defaultSanitizePopulate","populate","traverseQueryPopulate","expandWildcardPopulate","getModel","path","set","parent"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remove-restricted-relations.d.ts","sourceRoot":"","sources":["../../../src/sanitize/visitors/remove-restricted-relations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;yBAStC,MAAM,OAAO,KAAG,OAAO;AAAvC,
|
|
1
|
+
{"version":3,"file":"remove-restricted-relations.d.ts","sourceRoot":"","sources":["../../../src/sanitize/visitors/remove-restricted-relations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;yBAStC,MAAM,OAAO,KAAG,OAAO;AAAvC,wBAcI"}
|
|
@@ -8,108 +8,150 @@ const ACTIONS_TO_VERIFY = [
|
|
|
8
8
|
'find'
|
|
9
9
|
];
|
|
10
10
|
const { CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE } = contentTypes.constants;
|
|
11
|
-
var removeRestrictedRelations = ((auth)=>
|
|
12
|
-
|
|
11
|
+
var removeRestrictedRelations = ((auth)=>(visitorOptions, visitorUtils)=>{
|
|
12
|
+
const { attribute } = visitorOptions;
|
|
13
|
+
// Deliberately not an `async` function. This visitor is invoked for every key of
|
|
14
|
+
// every node of every entity, and only relation attributes need any work at all.
|
|
15
|
+
// Declaring it `async` meant each of those keys allocated a promise purely to
|
|
16
|
+
// resolve it immediately, and the traversal then awaited it. Returning early
|
|
17
|
+
// without a promise lets the traversal stay synchronous for the common case.
|
|
18
|
+
if (!attribute || attribute.type !== 'relation') {
|
|
13
19
|
return;
|
|
14
20
|
}
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
return visitRelationAttribute(auth, visitorOptions, visitorUtils);
|
|
22
|
+
});
|
|
23
|
+
const visitRelationAttribute = async (auth, { data, key, attribute, schema }, { remove, set })=>{
|
|
24
|
+
// Narrowing only; the caller reaches this for relation attributes exclusively.
|
|
25
|
+
if (!attribute) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const handleMorphRelation = async ()=>{
|
|
29
|
+
const elements = data[key];
|
|
30
|
+
if (!elements) {
|
|
17
31
|
return;
|
|
18
32
|
}
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
if ('connect' in elements || 'set' in elements || 'disconnect' in elements) {
|
|
34
|
+
const newValue = {};
|
|
35
|
+
const connect = await handleMorphElements(elements.connect || []);
|
|
36
|
+
const relSet = await handleMorphElements(elements.set || []);
|
|
37
|
+
const disconnect = await handleMorphElements(elements.disconnect || []);
|
|
38
|
+
if (connect.length > 0) {
|
|
39
|
+
newValue.connect = connect;
|
|
23
40
|
}
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
const connect = await handleMorphElements(elements.connect || []);
|
|
27
|
-
const relSet = await handleMorphElements(elements.set || []);
|
|
28
|
-
const disconnect = await handleMorphElements(elements.disconnect || []);
|
|
29
|
-
if (connect.length > 0) {
|
|
30
|
-
newValue.connect = connect;
|
|
31
|
-
}
|
|
32
|
-
if (relSet.length > 0) {
|
|
33
|
-
newValue.set = relSet;
|
|
34
|
-
}
|
|
35
|
-
if (disconnect.length > 0) {
|
|
36
|
-
newValue.disconnect = disconnect;
|
|
37
|
-
}
|
|
38
|
-
// TODO: this should technically be in its own visitor to check morph options, but for now we'll handle it here
|
|
39
|
-
if ('options' in elements && typeof elements.options === 'object' && elements.options !== null) {
|
|
40
|
-
const filteredOptions = {};
|
|
41
|
-
// Iterate through the keys of elements.options
|
|
42
|
-
Object.keys(elements.options).forEach((key)=>{
|
|
43
|
-
const validator = relations.VALID_RELATION_ORDERING_KEYS[key];
|
|
44
|
-
// Ensure the key exists in VALID_RELATION_ORDERING_KEYS and the validator is defined before calling it
|
|
45
|
-
if (validator && validator(elements.options[key])) {
|
|
46
|
-
filteredOptions[key] = elements.options[key];
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
// Assign the filtered options back to newValue
|
|
50
|
-
newValue.options = filteredOptions;
|
|
51
|
-
} else {
|
|
52
|
-
newValue.options = {};
|
|
53
|
-
}
|
|
54
|
-
set(key, newValue);
|
|
55
|
-
} else {
|
|
56
|
-
const newMorphValue = await handleMorphElements(elements);
|
|
57
|
-
if (newMorphValue.length) {
|
|
58
|
-
set(key, newMorphValue);
|
|
59
|
-
}
|
|
41
|
+
if (relSet.length > 0) {
|
|
42
|
+
newValue.set = relSet;
|
|
60
43
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
if (disconnect.length > 0) {
|
|
45
|
+
newValue.disconnect = disconnect;
|
|
46
|
+
}
|
|
47
|
+
// TODO: this should technically be in its own visitor to check morph options, but for now we'll handle it here
|
|
48
|
+
if ('options' in elements && typeof elements.options === 'object' && elements.options !== null) {
|
|
49
|
+
const filteredOptions = {};
|
|
50
|
+
// Iterate through the keys of elements.options
|
|
51
|
+
Object.keys(elements.options).forEach((key)=>{
|
|
52
|
+
const validator = relations.VALID_RELATION_ORDERING_KEYS[key];
|
|
53
|
+
// Ensure the key exists in VALID_RELATION_ORDERING_KEYS and the validator is defined before calling it
|
|
54
|
+
if (validator && validator(elements.options[key])) {
|
|
55
|
+
filteredOptions[key] = elements.options[key];
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// Assign the filtered options back to newValue
|
|
59
|
+
newValue.options = filteredOptions;
|
|
60
|
+
} else {
|
|
61
|
+
newValue.options = {};
|
|
66
62
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
73
|
-
if (isAllowed) {
|
|
74
|
-
allowedElements.push(element);
|
|
75
|
-
}
|
|
63
|
+
set(key, newValue);
|
|
64
|
+
} else {
|
|
65
|
+
const newMorphValue = await handleMorphElements(elements);
|
|
66
|
+
if (newMorphValue.length) {
|
|
67
|
+
set(key, newMorphValue);
|
|
76
68
|
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const handleMorphElements = async (elements)=>{
|
|
72
|
+
const allowedElements = [];
|
|
73
|
+
if (!fp.isArray(elements)) {
|
|
77
74
|
return allowedElements;
|
|
78
|
-
}
|
|
79
|
-
const
|
|
80
|
-
|
|
75
|
+
}
|
|
76
|
+
for (const element of elements){
|
|
77
|
+
if (!fp.isObject(element) || !('__type' in element)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const scopes = ACTIONS_TO_VERIFY.map((action)=>`${element.__type}.${action}`);
|
|
81
81
|
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
remove(key);
|
|
82
|
+
if (isAllowed) {
|
|
83
|
+
allowedElements.push(element);
|
|
85
84
|
}
|
|
86
|
-
};
|
|
87
|
-
const isCreatorRelation = [
|
|
88
|
-
CREATED_BY_ATTRIBUTE,
|
|
89
|
-
UPDATED_BY_ATTRIBUTE
|
|
90
|
-
].includes(key);
|
|
91
|
-
// Polymorphic relations
|
|
92
|
-
if (contentTypes.isMorphToRelationalAttribute(attribute)) {
|
|
93
|
-
await handleMorphRelation();
|
|
94
|
-
return;
|
|
95
85
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
86
|
+
return allowedElements;
|
|
87
|
+
};
|
|
88
|
+
const handleRegularRelation = async ()=>{
|
|
89
|
+
const scopes = ACTIONS_TO_VERIFY.map((action)=>`${attribute.target}.${action}`);
|
|
90
|
+
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
91
|
+
// If the authenticated user don't have access to any of the scopes, then remove the field
|
|
92
|
+
if (!isAllowed) {
|
|
93
|
+
remove(key);
|
|
100
94
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
};
|
|
96
|
+
const isCreatorRelation = [
|
|
97
|
+
CREATED_BY_ATTRIBUTE,
|
|
98
|
+
UPDATED_BY_ATTRIBUTE
|
|
99
|
+
].includes(key);
|
|
100
|
+
// Polymorphic relations
|
|
101
|
+
if (contentTypes.isMorphToRelationalAttribute(attribute)) {
|
|
102
|
+
await handleMorphRelation();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Creator relations
|
|
106
|
+
if (isCreatorRelation && schema.options?.populateCreatorFields) {
|
|
107
|
+
// do nothing
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Regular relations
|
|
111
|
+
await handleRegularRelation();
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Per-`auth` memo of scope decisions.
|
|
115
|
+
*
|
|
116
|
+
* The decision for a given scope depends only on the ability carried by `auth`, which is
|
|
117
|
+
* built once per request and not mutated while the response is sanitized. Sanitizing a
|
|
118
|
+
* list re-asks the same question for every entity, so a 25-entity page with four
|
|
119
|
+
* relations asked ~100 identical questions; each denial also constructed an Error, and
|
|
120
|
+
* capturing a stack trace is by far the most expensive part of that.
|
|
121
|
+
*
|
|
122
|
+
* Keyed weakly on the `auth` object so entries are collected with the request.
|
|
123
|
+
*/ const scopeDecisionCache = new WeakMap();
|
|
124
|
+
const canAccessScope = async (scope, auth)=>{
|
|
125
|
+
const cacheable = typeof auth === 'object' && auth !== null;
|
|
126
|
+
let decisions;
|
|
127
|
+
if (cacheable) {
|
|
128
|
+
decisions = scopeDecisionCache.get(auth);
|
|
129
|
+
if (decisions === undefined) {
|
|
130
|
+
decisions = new Map();
|
|
131
|
+
scopeDecisionCache.set(auth, decisions);
|
|
132
|
+
} else {
|
|
133
|
+
const cached = decisions.get(scope);
|
|
134
|
+
if (cached !== undefined) {
|
|
135
|
+
return cached;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let allowed;
|
|
140
|
+
try {
|
|
141
|
+
await strapi.auth.verify(auth, {
|
|
142
|
+
scope
|
|
143
|
+
});
|
|
144
|
+
allowed = true;
|
|
145
|
+
} catch {
|
|
146
|
+
allowed = false;
|
|
147
|
+
}
|
|
148
|
+
decisions?.set(scope, allowed);
|
|
149
|
+
return allowed;
|
|
150
|
+
};
|
|
104
151
|
const hasAccessToSomeScopes = async (scopes, auth)=>{
|
|
105
152
|
for (const scope of scopes){
|
|
106
|
-
|
|
107
|
-
await strapi.auth.verify(auth, {
|
|
108
|
-
scope
|
|
109
|
-
});
|
|
153
|
+
if (await canAccessScope(scope, auth)) {
|
|
110
154
|
return true;
|
|
111
|
-
} catch {
|
|
112
|
-
continue;
|
|
113
155
|
}
|
|
114
156
|
}
|
|
115
157
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remove-restricted-relations.js","sources":["../../../src/sanitize/visitors/remove-restricted-relations.ts"],"sourcesContent":["import { isArray, isObject } from 'lodash/fp';\nimport * as contentTypeUtils from '../../content-types';\nimport type { Visitor } from '../../traverse/factory';\nimport { RelationOrderingOptions } from '../../types';\nimport { VALID_RELATION_ORDERING_KEYS } from '../../relations';\n\nconst ACTIONS_TO_VERIFY = ['find'];\nconst { CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE } = contentTypeUtils.constants;\n\ntype MorphArray = Array<{ __type: string }>;\n\nexport default (auth: unknown): Visitor =>\n async ({ data, key, attribute, schema }, { remove, set }) => {\n if (!attribute) {\n return;\n }\n\n const isRelation = attribute.type === 'relation';\n\n if (!isRelation) {\n return;\n }\n\n const handleMorphRelation = async () => {\n const elements: any = (data as Record<string, MorphArray>)[key];\n\n if (!elements) {\n return;\n }\n\n if ('connect' in elements || 'set' in elements || 'disconnect' in elements) {\n const newValue: Record<string, unknown> = {};\n\n const connect = await handleMorphElements(elements.connect || []);\n const relSet = await handleMorphElements(elements.set || []);\n const disconnect = await handleMorphElements(elements.disconnect || []);\n\n if (connect.length > 0) {\n newValue.connect = connect;\n }\n\n if (relSet.length > 0) {\n newValue.set = relSet;\n }\n\n if (disconnect.length > 0) {\n newValue.disconnect = disconnect;\n }\n\n // TODO: this should technically be in its own visitor to check morph options, but for now we'll handle it here\n if (\n 'options' in elements &&\n typeof elements.options === 'object' &&\n elements.options !== null\n ) {\n const filteredOptions: RelationOrderingOptions = {};\n\n // Iterate through the keys of elements.options\n Object.keys(elements.options).forEach((key) => {\n const validator = VALID_RELATION_ORDERING_KEYS[key as keyof RelationOrderingOptions];\n\n // Ensure the key exists in VALID_RELATION_ORDERING_KEYS and the validator is defined before calling it\n if (validator && validator(elements.options[key])) {\n filteredOptions[key as keyof RelationOrderingOptions] = elements.options[key];\n }\n });\n\n // Assign the filtered options back to newValue\n newValue.options = filteredOptions;\n } else {\n newValue.options = {};\n }\n\n set(key, newValue);\n } else {\n const newMorphValue = await handleMorphElements(elements);\n\n if (newMorphValue.length) {\n set(key, newMorphValue);\n }\n }\n };\n\n const handleMorphElements = async (elements: any[]) => {\n const allowedElements: Record<string, unknown>[] = [];\n\n if (!isArray(elements)) {\n return allowedElements;\n }\n\n for (const element of elements) {\n if (!isObject(element) || !('__type' in element)) {\n continue;\n }\n\n const scopes = ACTIONS_TO_VERIFY.map((action) => `${element.__type}.${action}`);\n const isAllowed = await hasAccessToSomeScopes(scopes, auth);\n\n if (isAllowed) {\n allowedElements.push(element);\n }\n }\n\n return allowedElements;\n };\n\n const handleRegularRelation = async () => {\n const scopes = ACTIONS_TO_VERIFY.map((action) => `${attribute.target}.${action}`);\n\n const isAllowed = await hasAccessToSomeScopes(scopes, auth);\n\n // If the authenticated user don't have access to any of the scopes, then remove the field\n if (!isAllowed) {\n remove(key);\n }\n };\n\n const isCreatorRelation = [CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE].includes(key);\n\n // Polymorphic relations\n if (contentTypeUtils.isMorphToRelationalAttribute(attribute)) {\n await handleMorphRelation();\n return;\n }\n\n // Creator relations\n if (isCreatorRelation && schema.options?.populateCreatorFields) {\n // do nothing\n return;\n }\n\n // Regular relations\n await handleRegularRelation();\n };\n\nconst hasAccessToSomeScopes = async (scopes: string[], auth: unknown) => {\n for (const scope of scopes) {\n try {\n await strapi.auth.verify(auth, { scope });\n return true;\n } catch {\n continue;\n }\n }\n\n return false;\n};\n"],"names":["ACTIONS_TO_VERIFY","CREATED_BY_ATTRIBUTE","UPDATED_BY_ATTRIBUTE","contentTypeUtils","auth","data","key","attribute","schema","remove","set","isRelation","type","handleMorphRelation","elements","newValue","connect","handleMorphElements","relSet","disconnect","length","options","filteredOptions","Object","keys","forEach","validator","VALID_RELATION_ORDERING_KEYS","newMorphValue","allowedElements","isArray","element","isObject","scopes","map","action","__type","isAllowed","hasAccessToSomeScopes","push","handleRegularRelation","target","isCreatorRelation","includes","populateCreatorFields","scope","strapi","verify"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"remove-restricted-relations.js","sources":["../../../src/sanitize/visitors/remove-restricted-relations.ts"],"sourcesContent":["import { isArray, isObject } from 'lodash/fp';\nimport * as contentTypeUtils from '../../content-types';\nimport type { Visitor } from '../../traverse/factory';\nimport { RelationOrderingOptions } from '../../types';\nimport { VALID_RELATION_ORDERING_KEYS } from '../../relations';\n\nconst ACTIONS_TO_VERIFY = ['find'];\nconst { CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE } = contentTypeUtils.constants;\n\ntype MorphArray = Array<{ __type: string }>;\n\nexport default (auth: unknown): Visitor =>\n (visitorOptions, visitorUtils) => {\n const { attribute } = visitorOptions;\n\n // Deliberately not an `async` function. This visitor is invoked for every key of\n // every node of every entity, and only relation attributes need any work at all.\n // Declaring it `async` meant each of those keys allocated a promise purely to\n // resolve it immediately, and the traversal then awaited it. Returning early\n // without a promise lets the traversal stay synchronous for the common case.\n if (!attribute || attribute.type !== 'relation') {\n return;\n }\n\n return visitRelationAttribute(auth, visitorOptions, visitorUtils) as unknown as void;\n };\n\nconst visitRelationAttribute = async (\n auth: unknown,\n { data, key, attribute, schema }: Parameters<Visitor>[0],\n { remove, set }: Parameters<Visitor>[1]\n) => {\n // Narrowing only; the caller reaches this for relation attributes exclusively.\n if (!attribute) {\n return;\n }\n\n const handleMorphRelation = async () => {\n const elements: any = (data as Record<string, MorphArray>)[key];\n\n if (!elements) {\n return;\n }\n\n if ('connect' in elements || 'set' in elements || 'disconnect' in elements) {\n const newValue: Record<string, unknown> = {};\n\n const connect = await handleMorphElements(elements.connect || []);\n const relSet = await handleMorphElements(elements.set || []);\n const disconnect = await handleMorphElements(elements.disconnect || []);\n\n if (connect.length > 0) {\n newValue.connect = connect;\n }\n\n if (relSet.length > 0) {\n newValue.set = relSet;\n }\n\n if (disconnect.length > 0) {\n newValue.disconnect = disconnect;\n }\n\n // TODO: this should technically be in its own visitor to check morph options, but for now we'll handle it here\n if (\n 'options' in elements &&\n typeof elements.options === 'object' &&\n elements.options !== null\n ) {\n const filteredOptions: RelationOrderingOptions = {};\n\n // Iterate through the keys of elements.options\n Object.keys(elements.options).forEach((key) => {\n const validator = VALID_RELATION_ORDERING_KEYS[key as keyof RelationOrderingOptions];\n\n // Ensure the key exists in VALID_RELATION_ORDERING_KEYS and the validator is defined before calling it\n if (validator && validator(elements.options[key])) {\n filteredOptions[key as keyof RelationOrderingOptions] = elements.options[key];\n }\n });\n\n // Assign the filtered options back to newValue\n newValue.options = filteredOptions;\n } else {\n newValue.options = {};\n }\n\n set(key, newValue);\n } else {\n const newMorphValue = await handleMorphElements(elements);\n\n if (newMorphValue.length) {\n set(key, newMorphValue);\n }\n }\n };\n\n const handleMorphElements = async (elements: any[]) => {\n const allowedElements: Record<string, unknown>[] = [];\n\n if (!isArray(elements)) {\n return allowedElements;\n }\n\n for (const element of elements) {\n if (!isObject(element) || !('__type' in element)) {\n continue;\n }\n\n const scopes = ACTIONS_TO_VERIFY.map((action) => `${element.__type}.${action}`);\n const isAllowed = await hasAccessToSomeScopes(scopes, auth);\n\n if (isAllowed) {\n allowedElements.push(element);\n }\n }\n\n return allowedElements;\n };\n\n const handleRegularRelation = async () => {\n const scopes = ACTIONS_TO_VERIFY.map((action) => `${attribute.target}.${action}`);\n\n const isAllowed = await hasAccessToSomeScopes(scopes, auth);\n\n // If the authenticated user don't have access to any of the scopes, then remove the field\n if (!isAllowed) {\n remove(key);\n }\n };\n\n const isCreatorRelation = [CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE].includes(key);\n\n // Polymorphic relations\n if (contentTypeUtils.isMorphToRelationalAttribute(attribute)) {\n await handleMorphRelation();\n return;\n }\n\n // Creator relations\n if (isCreatorRelation && schema.options?.populateCreatorFields) {\n // do nothing\n return;\n }\n\n // Regular relations\n await handleRegularRelation();\n};\n\n/**\n * Per-`auth` memo of scope decisions.\n *\n * The decision for a given scope depends only on the ability carried by `auth`, which is\n * built once per request and not mutated while the response is sanitized. Sanitizing a\n * list re-asks the same question for every entity, so a 25-entity page with four\n * relations asked ~100 identical questions; each denial also constructed an Error, and\n * capturing a stack trace is by far the most expensive part of that.\n *\n * Keyed weakly on the `auth` object so entries are collected with the request.\n */\nconst scopeDecisionCache = new WeakMap<object, Map<string, boolean>>();\n\nconst canAccessScope = async (scope: string, auth: unknown): Promise<boolean> => {\n const cacheable = typeof auth === 'object' && auth !== null;\n\n let decisions: Map<string, boolean> | undefined;\n\n if (cacheable) {\n decisions = scopeDecisionCache.get(auth as object);\n\n if (decisions === undefined) {\n decisions = new Map();\n scopeDecisionCache.set(auth as object, decisions);\n } else {\n const cached = decisions.get(scope);\n if (cached !== undefined) {\n return cached;\n }\n }\n }\n\n let allowed: boolean;\n try {\n await strapi.auth.verify(auth, { scope });\n allowed = true;\n } catch {\n allowed = false;\n }\n\n decisions?.set(scope, allowed);\n\n return allowed;\n};\n\nconst hasAccessToSomeScopes = async (scopes: string[], auth: unknown) => {\n for (const scope of scopes) {\n if (await canAccessScope(scope, auth)) {\n return true;\n }\n }\n\n return false;\n};\n"],"names":["ACTIONS_TO_VERIFY","CREATED_BY_ATTRIBUTE","UPDATED_BY_ATTRIBUTE","contentTypeUtils","auth","visitorOptions","visitorUtils","attribute","type","visitRelationAttribute","data","key","schema","remove","set","handleMorphRelation","elements","newValue","connect","handleMorphElements","relSet","disconnect","length","options","filteredOptions","Object","keys","forEach","validator","VALID_RELATION_ORDERING_KEYS","newMorphValue","allowedElements","isArray","element","isObject","scopes","map","action","__type","isAllowed","hasAccessToSomeScopes","push","handleRegularRelation","target","isCreatorRelation","includes","populateCreatorFields","scopeDecisionCache","WeakMap","canAccessScope","scope","cacheable","decisions","get","undefined","Map","cached","allowed","strapi","verify"],"mappings":"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"}
|
|
@@ -6,108 +6,150 @@ const ACTIONS_TO_VERIFY = [
|
|
|
6
6
|
'find'
|
|
7
7
|
];
|
|
8
8
|
const { CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE } = constants;
|
|
9
|
-
var removeRestrictedRelations = ((auth)=>
|
|
10
|
-
|
|
9
|
+
var removeRestrictedRelations = ((auth)=>(visitorOptions, visitorUtils)=>{
|
|
10
|
+
const { attribute } = visitorOptions;
|
|
11
|
+
// Deliberately not an `async` function. This visitor is invoked for every key of
|
|
12
|
+
// every node of every entity, and only relation attributes need any work at all.
|
|
13
|
+
// Declaring it `async` meant each of those keys allocated a promise purely to
|
|
14
|
+
// resolve it immediately, and the traversal then awaited it. Returning early
|
|
15
|
+
// without a promise lets the traversal stay synchronous for the common case.
|
|
16
|
+
if (!attribute || attribute.type !== 'relation') {
|
|
11
17
|
return;
|
|
12
18
|
}
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
return visitRelationAttribute(auth, visitorOptions, visitorUtils);
|
|
20
|
+
});
|
|
21
|
+
const visitRelationAttribute = async (auth, { data, key, attribute, schema }, { remove, set })=>{
|
|
22
|
+
// Narrowing only; the caller reaches this for relation attributes exclusively.
|
|
23
|
+
if (!attribute) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const handleMorphRelation = async ()=>{
|
|
27
|
+
const elements = data[key];
|
|
28
|
+
if (!elements) {
|
|
15
29
|
return;
|
|
16
30
|
}
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
if ('connect' in elements || 'set' in elements || 'disconnect' in elements) {
|
|
32
|
+
const newValue = {};
|
|
33
|
+
const connect = await handleMorphElements(elements.connect || []);
|
|
34
|
+
const relSet = await handleMorphElements(elements.set || []);
|
|
35
|
+
const disconnect = await handleMorphElements(elements.disconnect || []);
|
|
36
|
+
if (connect.length > 0) {
|
|
37
|
+
newValue.connect = connect;
|
|
21
38
|
}
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
const connect = await handleMorphElements(elements.connect || []);
|
|
25
|
-
const relSet = await handleMorphElements(elements.set || []);
|
|
26
|
-
const disconnect = await handleMorphElements(elements.disconnect || []);
|
|
27
|
-
if (connect.length > 0) {
|
|
28
|
-
newValue.connect = connect;
|
|
29
|
-
}
|
|
30
|
-
if (relSet.length > 0) {
|
|
31
|
-
newValue.set = relSet;
|
|
32
|
-
}
|
|
33
|
-
if (disconnect.length > 0) {
|
|
34
|
-
newValue.disconnect = disconnect;
|
|
35
|
-
}
|
|
36
|
-
// TODO: this should technically be in its own visitor to check morph options, but for now we'll handle it here
|
|
37
|
-
if ('options' in elements && typeof elements.options === 'object' && elements.options !== null) {
|
|
38
|
-
const filteredOptions = {};
|
|
39
|
-
// Iterate through the keys of elements.options
|
|
40
|
-
Object.keys(elements.options).forEach((key)=>{
|
|
41
|
-
const validator = VALID_RELATION_ORDERING_KEYS[key];
|
|
42
|
-
// Ensure the key exists in VALID_RELATION_ORDERING_KEYS and the validator is defined before calling it
|
|
43
|
-
if (validator && validator(elements.options[key])) {
|
|
44
|
-
filteredOptions[key] = elements.options[key];
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
// Assign the filtered options back to newValue
|
|
48
|
-
newValue.options = filteredOptions;
|
|
49
|
-
} else {
|
|
50
|
-
newValue.options = {};
|
|
51
|
-
}
|
|
52
|
-
set(key, newValue);
|
|
53
|
-
} else {
|
|
54
|
-
const newMorphValue = await handleMorphElements(elements);
|
|
55
|
-
if (newMorphValue.length) {
|
|
56
|
-
set(key, newMorphValue);
|
|
57
|
-
}
|
|
39
|
+
if (relSet.length > 0) {
|
|
40
|
+
newValue.set = relSet;
|
|
58
41
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
if (disconnect.length > 0) {
|
|
43
|
+
newValue.disconnect = disconnect;
|
|
44
|
+
}
|
|
45
|
+
// TODO: this should technically be in its own visitor to check morph options, but for now we'll handle it here
|
|
46
|
+
if ('options' in elements && typeof elements.options === 'object' && elements.options !== null) {
|
|
47
|
+
const filteredOptions = {};
|
|
48
|
+
// Iterate through the keys of elements.options
|
|
49
|
+
Object.keys(elements.options).forEach((key)=>{
|
|
50
|
+
const validator = VALID_RELATION_ORDERING_KEYS[key];
|
|
51
|
+
// Ensure the key exists in VALID_RELATION_ORDERING_KEYS and the validator is defined before calling it
|
|
52
|
+
if (validator && validator(elements.options[key])) {
|
|
53
|
+
filteredOptions[key] = elements.options[key];
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Assign the filtered options back to newValue
|
|
57
|
+
newValue.options = filteredOptions;
|
|
58
|
+
} else {
|
|
59
|
+
newValue.options = {};
|
|
64
60
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
71
|
-
if (isAllowed) {
|
|
72
|
-
allowedElements.push(element);
|
|
73
|
-
}
|
|
61
|
+
set(key, newValue);
|
|
62
|
+
} else {
|
|
63
|
+
const newMorphValue = await handleMorphElements(elements);
|
|
64
|
+
if (newMorphValue.length) {
|
|
65
|
+
set(key, newMorphValue);
|
|
74
66
|
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const handleMorphElements = async (elements)=>{
|
|
70
|
+
const allowedElements = [];
|
|
71
|
+
if (!isArray(elements)) {
|
|
75
72
|
return allowedElements;
|
|
76
|
-
}
|
|
77
|
-
const
|
|
78
|
-
|
|
73
|
+
}
|
|
74
|
+
for (const element of elements){
|
|
75
|
+
if (!isObject(element) || !('__type' in element)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const scopes = ACTIONS_TO_VERIFY.map((action)=>`${element.__type}.${action}`);
|
|
79
79
|
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
remove(key);
|
|
80
|
+
if (isAllowed) {
|
|
81
|
+
allowedElements.push(element);
|
|
83
82
|
}
|
|
84
|
-
};
|
|
85
|
-
const isCreatorRelation = [
|
|
86
|
-
CREATED_BY_ATTRIBUTE,
|
|
87
|
-
UPDATED_BY_ATTRIBUTE
|
|
88
|
-
].includes(key);
|
|
89
|
-
// Polymorphic relations
|
|
90
|
-
if (isMorphToRelationalAttribute(attribute)) {
|
|
91
|
-
await handleMorphRelation();
|
|
92
|
-
return;
|
|
93
83
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
return allowedElements;
|
|
85
|
+
};
|
|
86
|
+
const handleRegularRelation = async ()=>{
|
|
87
|
+
const scopes = ACTIONS_TO_VERIFY.map((action)=>`${attribute.target}.${action}`);
|
|
88
|
+
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
89
|
+
// If the authenticated user don't have access to any of the scopes, then remove the field
|
|
90
|
+
if (!isAllowed) {
|
|
91
|
+
remove(key);
|
|
98
92
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
};
|
|
94
|
+
const isCreatorRelation = [
|
|
95
|
+
CREATED_BY_ATTRIBUTE,
|
|
96
|
+
UPDATED_BY_ATTRIBUTE
|
|
97
|
+
].includes(key);
|
|
98
|
+
// Polymorphic relations
|
|
99
|
+
if (isMorphToRelationalAttribute(attribute)) {
|
|
100
|
+
await handleMorphRelation();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Creator relations
|
|
104
|
+
if (isCreatorRelation && schema.options?.populateCreatorFields) {
|
|
105
|
+
// do nothing
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Regular relations
|
|
109
|
+
await handleRegularRelation();
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Per-`auth` memo of scope decisions.
|
|
113
|
+
*
|
|
114
|
+
* The decision for a given scope depends only on the ability carried by `auth`, which is
|
|
115
|
+
* built once per request and not mutated while the response is sanitized. Sanitizing a
|
|
116
|
+
* list re-asks the same question for every entity, so a 25-entity page with four
|
|
117
|
+
* relations asked ~100 identical questions; each denial also constructed an Error, and
|
|
118
|
+
* capturing a stack trace is by far the most expensive part of that.
|
|
119
|
+
*
|
|
120
|
+
* Keyed weakly on the `auth` object so entries are collected with the request.
|
|
121
|
+
*/ const scopeDecisionCache = new WeakMap();
|
|
122
|
+
const canAccessScope = async (scope, auth)=>{
|
|
123
|
+
const cacheable = typeof auth === 'object' && auth !== null;
|
|
124
|
+
let decisions;
|
|
125
|
+
if (cacheable) {
|
|
126
|
+
decisions = scopeDecisionCache.get(auth);
|
|
127
|
+
if (decisions === undefined) {
|
|
128
|
+
decisions = new Map();
|
|
129
|
+
scopeDecisionCache.set(auth, decisions);
|
|
130
|
+
} else {
|
|
131
|
+
const cached = decisions.get(scope);
|
|
132
|
+
if (cached !== undefined) {
|
|
133
|
+
return cached;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
let allowed;
|
|
138
|
+
try {
|
|
139
|
+
await strapi.auth.verify(auth, {
|
|
140
|
+
scope
|
|
141
|
+
});
|
|
142
|
+
allowed = true;
|
|
143
|
+
} catch {
|
|
144
|
+
allowed = false;
|
|
145
|
+
}
|
|
146
|
+
decisions?.set(scope, allowed);
|
|
147
|
+
return allowed;
|
|
148
|
+
};
|
|
102
149
|
const hasAccessToSomeScopes = async (scopes, auth)=>{
|
|
103
150
|
for (const scope of scopes){
|
|
104
|
-
|
|
105
|
-
await strapi.auth.verify(auth, {
|
|
106
|
-
scope
|
|
107
|
-
});
|
|
151
|
+
if (await canAccessScope(scope, auth)) {
|
|
108
152
|
return true;
|
|
109
|
-
} catch {
|
|
110
|
-
continue;
|
|
111
153
|
}
|
|
112
154
|
}
|
|
113
155
|
return false;
|