Add payload validation on create / validate

This commit is contained in:
rijkvanzanten
2020-07-15 15:01:09 -04:00
parent 699014e715
commit 55332f72d5
3 changed files with 125 additions and 6 deletions

View File

@@ -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);

View File

@@ -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
View 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();
}