@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
85 lines (84 loc) • 4.25 kB
JavaScript
import { parseFilter, validatePayload } from '@directus/utils';
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
import { assign, difference, uniq } from 'lodash-es';
import { fetchPermissions } from '../../lib/fetch-permissions.js';
import { fetchPolicies } from '../../lib/fetch-policies.js';
import { extractRequiredDynamicVariableContext } from '../../utils/extract-required-dynamic-variable-context.js';
import { fetchDynamicVariableData } from '../../utils/fetch-dynamic-variable-data.js';
import { contextHasDynamicVariables } from '../process-ast/utils/context-has-dynamic-variables.js';
import { isFieldNullable } from './lib/is-field-nullable.js';
import { createCollectionForbiddenError, createFieldsForbiddenError, } from '../process-ast/utils/validate-path/create-error.js';
/**
* @note this only validates the top-level fields. The expectation is that this function is called
* for each level of nested insert separately
*/
export async function processPayload(options, context) {
let permissions;
let permissionValidationRules = [];
let policies = [];
if (!options.accountability.admin) {
policies = await fetchPolicies(options.accountability, context);
permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
if (permissions.length === 0) {
throw createCollectionForbiddenError('', options.collection);
}
const fieldsAllowed = uniq(permissions.map(({ fields }) => fields ?? []).flat());
if (fieldsAllowed.includes('*') === false) {
const fieldsUsed = Object.keys(options.payload);
const notAllowed = difference(fieldsUsed, fieldsAllowed);
if (notAllowed.length > 0) {
throw createFieldsForbiddenError('', options.collection, notAllowed);
}
}
permissionValidationRules = permissions.map(({ validation }) => validation);
}
const fields = Object.values(context.schema.collections[options.collection]?.fields ?? {});
const fieldValidationRules = [];
for (const field of fields) {
if (!isFieldNullable(field)) {
const isSubmissionRequired = options.action === 'create' && field.defaultValue === null;
if (isSubmissionRequired) {
fieldValidationRules.push({
[field.field]: {
_submitted: true,
},
});
}
fieldValidationRules.push({
[field.field]: {
_nnull: true,
},
});
}
if (field.validation) {
const permissionContext = extractRequiredDynamicVariableContext(field.validation);
const filterContext = contextHasDynamicVariables(permissionContext)
? await fetchDynamicVariableData({
accountability: options.accountability,
policies,
dynamicVariableContext: permissionContext,
}, context)
: undefined;
const validationFilter = parseFilter(field.validation, options.accountability, filterContext);
fieldValidationRules.push(validationFilter);
}
}
const presets = (permissions ?? []).map((permission) => permission.presets);
const payloadWithPresets = assign({}, ...presets, options.payload);
const validationRules = [...fieldValidationRules, ...permissionValidationRules].filter((rule) => {
if (rule === null)
return false;
if (Object.keys(rule).length === 0)
return false;
return true;
});
if (validationRules.length > 0) {
const validationErrors = [];
validationErrors.push(...validatePayload({ _and: validationRules }, payloadWithPresets)
.map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details, options.nested))))
.flat());
if (validationErrors.length > 0)
throw validationErrors;
}
return payloadWithPresets;
}