1
import type { Permission, PermissionsAction, SchemaOverview } from '@directus/types';
2
import { uniq } from 'lodash-es';
5
* Reduces the schema based on the included permissions. The resulting object is the schema structure, but with only
6
* the allowed collections/fields/relations included based on the permissions.
7
* @param schema The full project schema
8
* @param actions Array of permissions actions (crud)
9
* @returns Reduced schema
11
export function reduceSchema(
12
schema: SchemaOverview,
13
permissions: Permission[] | null,
14
actions: PermissionsAction[] = ['create', 'read', 'update', 'delete'],
16
const reduced: SchemaOverview = {
21
const allowedFieldsInCollection =
23
?.filter((permission) => actions.includes(permission.action))
25
(acc, permission) => {
26
if (!acc[permission.collection]) {
27
acc[permission.collection] = [];
30
if (permission.fields) {
31
acc[permission.collection] = uniq([...acc[permission.collection]!, ...permission.fields]);
36
{} as { [collection: string]: string[] },
39
for (const [collectionName, collection] of Object.entries(schema.collections)) {
42
(permission) => permission.collection === collectionName && actions.includes(permission.action),
48
const fields: SchemaOverview['collections'][string]['fields'] = {};
50
for (const [fieldName, field] of Object.entries(schema.collections[collectionName]!.fields)) {
52
!allowedFieldsInCollection[collectionName]?.includes('*') &&
53
!allowedFieldsInCollection[collectionName]?.includes(fieldName)
58
const o2mRelation = schema.relations.find(
59
(relation) => relation.related_collection === collectionName && relation.meta?.one_field === fieldName,
65
(permission) => permission.collection === o2mRelation.collection && actions.includes(permission.action),
71
fields[fieldName] = field;
74
reduced.collections[collectionName] = {
80
reduced.relations = schema.relations.filter((relation) => {
81
let collectionsAllowed = true;
82
let fieldsAllowed = true;
84
if (Object.keys(allowedFieldsInCollection).includes(relation.collection) === false) {
85
collectionsAllowed = false;
89
relation.related_collection &&
90
(Object.keys(allowedFieldsInCollection).includes(relation.related_collection) === false ||
91
// Ignore legacy permissions with an empty fields array
92
allowedFieldsInCollection[relation.related_collection]?.length === 0)
94
collectionsAllowed = false;
98
relation.meta?.one_allowed_collections &&
99
relation.meta.one_allowed_collections.every((collection) =>
100
Object.keys(allowedFieldsInCollection).includes(collection),
103
collectionsAllowed = false;
107
!allowedFieldsInCollection[relation.collection] ||
108
(allowedFieldsInCollection[relation.collection]?.includes('*') === false &&
109
allowedFieldsInCollection[relation.collection]?.includes(relation.field) === false)
111
fieldsAllowed = false;
115
relation.related_collection &&
116
relation.meta?.one_field &&
117
(!allowedFieldsInCollection[relation.related_collection] ||
118
(allowedFieldsInCollection[relation.related_collection]?.includes('*') === false &&
119
allowedFieldsInCollection[relation.related_collection]?.includes(relation.meta?.one_field) === false))
121
fieldsAllowed = false;
124
return collectionsAllowed && fieldsAllowed;