payload
Version: 
Node, React, Headless CMS and Application Framework built on Next.js
151 lines (150 loc) • 6.09 kB
JavaScript
// @ts-strict-ignore
import { fieldAffectsData, fieldIsVirtual } from '../../fields/config/types.js';
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js';
import isolateObjectProperty from '../../utilities/isolateObjectProperty.js';
import { getLocalizedPaths } from '../getLocalizedPaths.js';
import { validateQueryPaths } from './validateQueryPaths.js';
/**
 * Validate the Payload key / value / operator
 */ export async function validateSearchParam({ collectionConfig, errors, fields, globalConfig, operator, overrideAccess, parentIsLocalized, path: incomingPath, policies, req, val, versionFields }) {
    // Replace GraphQL nested field double underscore formatting
    let sanitizedPath;
    if (incomingPath === '_id') {
        sanitizedPath = 'id';
    } else {
        sanitizedPath = incomingPath.replace(/__/g, '.');
    }
    let paths = [];
    const { slug } = collectionConfig || globalConfig;
    const blockPolicies = {};
    if (globalConfig && !policies.globals[slug]) {
        policies.globals[slug] = await getEntityPolicies({
            type: 'global',
            blockPolicies,
            entity: globalConfig,
            operations: [
                'read'
            ],
            req
        });
    }
    if (sanitizedPath !== 'id') {
        paths = getLocalizedPaths({
            collectionSlug: collectionConfig?.slug,
            fields,
            globalSlug: globalConfig?.slug,
            incomingPath: sanitizedPath,
            locale: req.locale,
            overrideAccess,
            parentIsLocalized,
            payload: req.payload
        });
    }
    const promises = [];
    // Sanitize relation.otherRelation.id to relation.otherRelation
    if (paths.at(-1)?.path === 'id') {
        const previousField = paths.at(-2)?.field;
        if (previousField && (previousField.type === 'relationship' || previousField.type === 'upload') && typeof previousField.relationTo === 'string') {
            paths.pop();
        }
    }
    promises.push(...paths.map(async ({ collectionSlug, field, invalid, path }, i)=>{
        if (invalid) {
            errors.push({
                path
            });
            return;
        }
        if (fieldIsVirtual(field)) {
            errors.push({
                path
            });
        }
        if (!overrideAccess && fieldAffectsData(field)) {
            if (collectionSlug) {
                if (!policies.collections[collectionSlug]) {
                    policies.collections[collectionSlug] = await getEntityPolicies({
                        type: 'collection',
                        blockPolicies,
                        entity: req.payload.collections[collectionSlug].config,
                        operations: [
                            'read'
                        ],
                        req: isolateObjectProperty(req, 'transactionID')
                    });
                }
                if ([
                    'hash',
                    'salt'
                ].includes(incomingPath) && collectionConfig.auth && !collectionConfig.auth?.disableLocalStrategy) {
                    errors.push({
                        path: incomingPath
                    });
                }
            }
            let fieldPath = path;
            // remove locale from end of path
            if (path.endsWith(`.${req.locale}`)) {
                fieldPath = path.slice(0, -(req.locale.length + 1));
            }
            // remove ".value" from ends of polymorphic relationship paths
            if ((field.type === 'relationship' || field.type === 'upload') && Array.isArray(field.relationTo)) {
                fieldPath = fieldPath.replace('.value', '');
            }
            const entityType = globalConfig ? 'globals' : 'collections';
            const entitySlug = collectionSlug || globalConfig.slug;
            const segments = fieldPath.split('.');
            let fieldAccess;
            if (versionFields) {
                fieldAccess = policies[entityType][entitySlug];
                if (segments[0] === 'parent' || segments[0] === 'version') {
                    segments.shift();
                }
            } else {
                fieldAccess = policies[entityType][entitySlug].fields;
            }
            segments.forEach((segment)=>{
                if (fieldAccess[segment]) {
                    if ('fields' in fieldAccess[segment]) {
                        fieldAccess = fieldAccess[segment].fields;
                    } else if ('blocks' in fieldAccess[segment] || 'blockReferences' in fieldAccess[segment]) {
                        fieldAccess = fieldAccess[segment];
                    } else {
                        fieldAccess = fieldAccess[segment];
                    }
                }
            });
            if (!fieldAccess?.read?.permission) {
                errors.push({
                    path: fieldPath
                });
            }
        }
        if (i > 1) {
            // Remove top collection and reverse array
            // to work backwards from top
            const pathsToQuery = paths.slice(1).reverse();
            pathsToQuery.forEach(({ collectionSlug: pathCollectionSlug, path: subPath }, pathToQueryIndex)=>{
                // On the "deepest" collection,
                // validate query of the relationship
                if (pathToQueryIndex === 0) {
                    promises.push(validateQueryPaths({
                        collectionConfig: req.payload.collections[pathCollectionSlug].config,
                        errors,
                        globalConfig: undefined,
                        overrideAccess,
                        policies,
                        req,
                        where: {
                            [subPath]: {
                                [operator]: val
                            }
                        }
                    }));
                }
            });
        }
    }));
    await Promise.all(promises);
}
//# sourceMappingURL=validateSearchParams.js.map