From a034e4477590d12232a1bf06557fc815898d15fd Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Tue, 7 Jul 2020 15:54:59 -0400 Subject: [PATCH] Work on field query validation a bit --- src/constants.ts | 3 ++- src/middleware/sanitize-query.ts | 7 ++++++ src/middleware/validate-query.ts | 42 ++++++++++++++++--------------- src/services/schema.ts | 17 ------------- src/utils/has-fields.ts | 43 ++++++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 38 deletions(-) delete mode 100644 src/services/schema.ts create mode 100644 src/utils/has-fields.ts diff --git a/src/constants.ts b/src/constants.ts index 7f3762367f..2f7123101f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,4 @@ import { Transformation } from './types/assets'; -import { Collection } from './types/collection'; export const SYSTEM_ASSET_WHITELIST: Transformation[] = [ { @@ -38,3 +37,5 @@ export const SYSTEM_ASSET_WHITELIST: Transformation[] = [ ]; export const ASSET_GENERATION_QUERY_KEYS = ['key', 'w', 'h', 'f']; + +export const FIELD_SPECIAL_ALIAS_TYPES = ['alias', 'o2m', 'm2m', 'file-info']; diff --git a/src/middleware/sanitize-query.ts b/src/middleware/sanitize-query.ts index 1c332c6b10..f81c07d025 100644 --- a/src/middleware/sanitize-query.ts +++ b/src/middleware/sanitize-query.ts @@ -26,6 +26,13 @@ const sanitizeQuery: RequestHandler = (req, res, next) => { if (req.query.limit) { query.limit = sanitizeLimit(req.query.limit); + } else { + /** @todo is this the right place to set these defaults? */ + query.limit = 100; + } + + if (req.query.limit == '-1') { + delete query.limit; } if (req.query.offset) { diff --git a/src/middleware/validate-query.ts b/src/middleware/validate-query.ts index 7d3fb7a0d2..d0d6d3c5bf 100644 --- a/src/middleware/validate-query.ts +++ b/src/middleware/validate-query.ts @@ -7,9 +7,9 @@ import { RequestHandler } from 'express'; import { Query } from '../types/query'; -import { hasFields } from '../services/schema'; import asyncHandler from 'express-async-handler'; import { InvalidQueryException } from '../exceptions'; +import hasFields from '../utils/has-fields'; const validateQuery: RequestHandler = asyncHandler(async (req, res, next) => { if (!req.collection) return next(); @@ -35,25 +35,27 @@ async function validateParams(collection: string, query: Query) { } async function validateFields(collection: string, query: Query) { - const fieldsToCheck = new Set(); - - if (query.fields) { - /** @todo support relationships in here */ - // query.fields.forEach((field) => fieldsToCheck.add(field)); - } - - if (query.sort) { - query.sort.forEach((sort) => fieldsToCheck.add(sort.column)); - } - - /** @todo swap with more efficient schemaInspector version */ - const fieldsExist = await hasFields(collection, Array.from(fieldsToCheck)); - - Array.from(fieldsToCheck).forEach((field, index) => { - const exists = fieldsExist[index]; - if (exists === false) - throw new InvalidQueryException(`Field ${field} doesn't exist in ${collection}.`); - }); + /** + * @todo combine this with permissions (?) + */ + /** + * @todo use /utils/has-fields + */ + // const fieldsToCheck = new Set(); + // if (query.fields) { + // /** @todo support relationships in here */ + // query.fields.forEach((field) => fieldsToCheck.add(field)); + // } + // if (query.sort) { + // query.sort.forEach((sort) => fieldsToCheck.add(sort.column)); + // } + // /** @todo swap with more efficient schemaInspector version */ + // const fieldsExist = await hasFields(collection, Array.from(fieldsToCheck)); + // Array.from(fieldsToCheck).forEach((field, index) => { + // const exists = fieldsExist[index]; + // if (exists === false) + // throw new InvalidQueryException(`Field ${field} doesn't exist in ${collection}.`); + // }); } async function validateMeta(query: Query) { diff --git a/src/services/schema.ts b/src/services/schema.ts deleted file mode 100644 index 706676ade1..0000000000 --- a/src/services/schema.ts +++ /dev/null @@ -1,17 +0,0 @@ -import database from '../database'; - -/** @TODO replace this with schema inspector */ - -export const hasCollection = async (collection: string) => { - return await database.schema.hasTable(collection); -}; - -export const hasField = async (collection: string, field: string) => { - return await database.schema.hasColumn(collection, field); -}; - -export const hasFields = async (collection: string, fields: string[]) => { - const columns = await database(collection).columnInfo(); - const fieldNames = Object.keys(columns); - return fields.map((fieldKey) => fieldNames.includes(fieldKey)); -}; diff --git a/src/utils/has-fields.ts b/src/utils/has-fields.ts new file mode 100644 index 0000000000..b1e47435f3 --- /dev/null +++ b/src/utils/has-fields.ts @@ -0,0 +1,43 @@ +import database, { schemaInspector } from '../database'; +import { FIELD_SPECIAL_ALIAS_TYPES } from '../constants'; +import { uniq } from 'lodash'; + +export default async function hasFields(fields: { collection: string; field: string }[]) { + const fieldsObject: { [collection: string]: string[] } = {}; + + fields.forEach(({ field, collection }) => { + if (fieldsObject.hasOwnProperty(collection) === false) { + fieldsObject[collection] = []; + } + + fieldsObject[collection].push(field); + }); + + await Promise.all( + Object.entries(fieldsObject).map(([collection, fields]) => + collectionHasFields(collection, fields) + ) + ); + return true; +} + +export async function collectionHasFields(collection: string, fieldKeys: string[]) { + const [columns, fields] = await Promise.all([ + schemaInspector.columns(collection), + database + .select('field') + .from('directus_fields') + .where({ collection }) + .whereIn('field', fieldKeys) + .whereIn('special', FIELD_SPECIAL_ALIAS_TYPES), + ]); + + const existingFields = uniq([ + ...columns.map(({ column }) => column), + ...fields.map(({ field }) => field), + ]); + + for (const key of fieldKeys) { + if (existingFields.includes(key) === false) throw new Error(key); + } +}