feat: validate relationship and upload ids (#1004)

* feat: validate relationship and upload ids

* chore: update fields-relationship test data

* fix: skip FE relationship and upload id validation
This commit is contained in:
Dan Ribbens
2022-08-29 14:57:06 -04:00
committed by GitHub
parent 50b0303ab3
commit d727fc8e24
6 changed files with 101 additions and 8 deletions

View File

@@ -1,6 +1,9 @@
import DataLoader, { BatchLoadFn } from 'dataloader';
import { PayloadRequest } from '../express/types';
import { TypeWithID } from '../globals/config/types';
import { isValidID } from '../utilities/isValidID';
import { getIDType } from '../utilities/getIDType';
import { fieldAffectsData } from '../fields/config/types';
// Payload uses `dataloader` to solve the classic GraphQL N+1 problem.
@@ -49,13 +52,18 @@ const batchAndLoadDocs = (req: PayloadRequest): BatchLoadFn<string, TypeWithID>
const batchKey = JSON.stringify(batchKeyArray);
return {
...batches,
[batchKey]: [
...batches[batchKey] || [],
id,
],
};
const idField = payload.collections?.[collection].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id');
if (isValidID(id, getIDType(idField))) {
return {
...batches,
[batchKey]: [
...batches[batchKey] || [],
id,
],
};
}
return batches;
}, {});
// Run find requests in parallel

View File

@@ -17,9 +17,12 @@ import {
TextField,
UploadField,
Validate,
fieldAffectsData,
} from './config/types';
import { TypeWithID } from '../collections/config/types';
import canUseDOM from '../utilities/canUseDOM';
import { isValidID } from '../utilities/isValidID';
import { getIDType } from '../utilities/getIDType';
const defaultMessage = 'This field is required.';
@@ -232,6 +235,15 @@ export const upload: Validate<unknown, unknown, UploadField> = (value: string, o
return defaultMessage;
}
if (!canUseDOM) {
const idField = options.payload.collections[options.relationTo].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id');
const type = getIDType(idField);
if (!isValidID(value, type)) {
return 'This field is not a valid upload ID';
}
}
return validateFilterOptions(value, options);
};
@@ -240,6 +252,46 @@ export const relationship: Validate<unknown, unknown, RelationshipField> = async
return defaultMessage;
}
if (!canUseDOM && typeof value !== 'undefined') {
const values = Array.isArray(value) ? value : [value];
const invalidRelationships = values.filter((val) => {
let collection: string;
let requestedID: string | number;
if (typeof options.relationTo === 'string') {
collection = options.relationTo;
// custom id
if (typeof val === 'string' || typeof val === 'number') {
requestedID = val;
}
}
if (Array.isArray(options.relationTo) && typeof val === 'object' && val?.relationTo) {
collection = val.relationTo;
requestedID = val.value;
}
const idField = options.payload.collections[collection].config.fields.find((field) => fieldAffectsData(field) && field.name === 'id');
let type;
if (idField) {
type = idField.type === 'number' ? 'number' : 'text';
} else {
type = 'ObjectID';
}
return !isValidID(requestedID, type);
});
if (invalidRelationships.length > 0) {
return `This field has the following invalid selections: ${
invalidRelationships.map((err, invalid) => {
return `${err} ${JSON.stringify(invalid)}`;
}).join(', ')}` as string;
}
}
return validateFilterOptions(value, options);
};

View File

@@ -0,0 +1,8 @@
import { Field } from '../fields/config/types';
export const getIDType = (idField: Field | null): 'number' | 'text' | 'ObjectID' => {
if (idField) {
return idField.type === 'number' ? 'number' : 'text';
}
return 'ObjectID';
};

View File

@@ -0,0 +1,9 @@
import ObjectID from 'bson-objectid';
export const isValidID = (value: string | number, type: 'text' | 'number' | 'ObjectID'): boolean => {
if (type === 'ObjectID') {
return ObjectID.isValid(String(value));
}
return (type === 'text' && typeof value === 'string')
|| (type === 'number' && typeof value === 'number' && !Number.isNaN(value));
};