mirror of
https://github.com/directus/directus.git
synced 2026-01-29 14:48:02 -05:00
Add payload validation on create / validate
This commit is contained in:
@@ -44,15 +44,24 @@ export const createItem = async (
|
||||
data: Record<string, any>,
|
||||
accountability?: Accountability
|
||||
): Promise<string | number> => {
|
||||
let payload = await PayloadService.processValues('create', collection, data);
|
||||
let payload = data;
|
||||
|
||||
if (accountability) {
|
||||
payload = await PermissionsService.processValues(
|
||||
'create',
|
||||
collection,
|
||||
accountability?.role,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
payload = await PayloadService.processValues('create', collection, payload);
|
||||
|
||||
payload = await PayloadService.processM2O(collection, payload);
|
||||
|
||||
const primaryKeyField = await schemaInspector.primary(collection);
|
||||
|
||||
// Only insert the values that actually save to an existing column. This ensures we ignore aliases etc
|
||||
const columns = await schemaInspector.columns(collection);
|
||||
|
||||
const payloadWithoutAlias = pick(
|
||||
payload,
|
||||
columns.map(({ column }) => column)
|
||||
@@ -130,7 +139,18 @@ export const updateItem = async (
|
||||
data: Record<string, any>,
|
||||
accountability?: Accountability
|
||||
): Promise<string | number> => {
|
||||
let payload = await PayloadService.processValues('update', collection, data);
|
||||
let payload = data;
|
||||
|
||||
if (accountability) {
|
||||
payload = await PermissionsService.processValues(
|
||||
'validate',
|
||||
collection,
|
||||
accountability?.role,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
payload = await PayloadService.processValues('update', collection, data);
|
||||
|
||||
payload = await PayloadService.processM2O(collection, payload);
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { Accountability, AST, NestedCollectionAST, FieldAST, Query, Permission } from '../types';
|
||||
import {
|
||||
Accountability,
|
||||
AST,
|
||||
NestedCollectionAST,
|
||||
FieldAST,
|
||||
Query,
|
||||
Permission,
|
||||
Operation,
|
||||
} from '../types';
|
||||
import * as ItemsService from './items';
|
||||
import database from '../database';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import { uniq } from 'lodash';
|
||||
import generateJoi from '../utils/generate-joi';
|
||||
import Joi from '@hapi/joi';
|
||||
|
||||
export const createPermission = async (
|
||||
data: Record<string, any>,
|
||||
@@ -165,3 +175,48 @@ export const processAST = async (ast: AST, role: string | null): Promise<AST> =>
|
||||
return ast;
|
||||
}
|
||||
};
|
||||
|
||||
export const processValues = async (
|
||||
operation: Operation,
|
||||
collection: string,
|
||||
role: string | null,
|
||||
data: Record<string, any>
|
||||
) => {
|
||||
const permission = await database
|
||||
.select<Permission>('*')
|
||||
.from('directus_permissions')
|
||||
.where({ operation, collection, role })
|
||||
.first();
|
||||
|
||||
if (!permission) throw new ForbiddenException();
|
||||
|
||||
const allowedFields = permission.fields.split(',');
|
||||
|
||||
if (allowedFields.includes('*') === false) {
|
||||
const keysInData = Object.keys(data);
|
||||
const invalidKeys = keysInData.filter(
|
||||
(fieldKey) => allowedFields.includes(fieldKey) === false
|
||||
);
|
||||
|
||||
if (invalidKeys.length > 0) {
|
||||
throw new InvalidPayloadException(`Field "${invalidKeys[0]}" doesn't exist.`);
|
||||
}
|
||||
}
|
||||
|
||||
const preset = permission.presets || {};
|
||||
|
||||
const payload = {
|
||||
...preset,
|
||||
...data,
|
||||
};
|
||||
|
||||
const schema = generateJoi(permission.permissions);
|
||||
|
||||
const { error } = schema.validate(payload);
|
||||
|
||||
if (error) {
|
||||
throw new InvalidPayloadException(error.message);
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
44
src/utils/generate-joi.ts
Normal file
44
src/utils/generate-joi.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Filter } from '../types';
|
||||
import Joi, { AnySchema } from '@hapi/joi';
|
||||
|
||||
export default function generateJoi(filter: Filter) {
|
||||
const schema: Record<string, AnySchema> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
const isField = key.startsWith('_') === false;
|
||||
|
||||
if (isField) {
|
||||
const operator = Object.keys(value)[0];
|
||||
|
||||
/** @TODO
|
||||
* - Extend with all operators
|
||||
*/
|
||||
|
||||
if (operator === '_eq') {
|
||||
schema[key] = Joi.any().equal(Object.values(value)[0]);
|
||||
}
|
||||
|
||||
if (operator === '_neq') {
|
||||
schema[key] = Joi.any().not(Object.values(value)[0]);
|
||||
}
|
||||
|
||||
if (operator === '_in') {
|
||||
schema[key] = Joi.any().equal(...(Object.values(value)[0] as (string | number)[]));
|
||||
}
|
||||
|
||||
if (operator === '_nin') {
|
||||
schema[key] = Joi.any().not(...(Object.values(value)[0] as (string | number)[]));
|
||||
}
|
||||
|
||||
if (operator === '_gt') {
|
||||
schema[key] = Joi.number().greater(Number(Object.values(value)[0]));
|
||||
}
|
||||
|
||||
if (operator === '_lt') {
|
||||
schema[key] = Joi.number().less(Number(Object.values(value)[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Joi.object(schema).unknown();
|
||||
}
|
||||
Reference in New Issue
Block a user