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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
8
src/utilities/getIDType.ts
Normal file
8
src/utilities/getIDType.ts
Normal 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';
|
||||
};
|
||||
9
src/utilities/isValidID.ts
Normal file
9
src/utilities/isValidID.ts
Normal 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));
|
||||
};
|
||||
Reference in New Issue
Block a user