UNPKG

payload

Version:

Node, React, Headless CMS and Application Framework built on Next.js

236 lines (235 loc) • 11.6 kB
import { tabHasName } from '../../fields/config/types.js'; const isThenable = (value)=>value != null && typeof value.then === 'function'; /** * Helper to set a permission value that might be a promise. * If it's a promise, creates a chained promise that resolves to update the target, * stores the promise temporarily, and adds it to the promises array for later resolution. */ const setPermission = (// eslint-disable-next-line @typescript-eslint/no-explicit-any target, operation, value, promises)=>{ if (isThenable(value)) { // Create a single permission object that will be mutated in place // This ensures all references (including cached blocks) see the resolved value const permissionObj = { permission: value }; target[operation] = permissionObj; const permissionPromise = value.then((result)=>{ // Mutate the permission property in place so all references see the update permissionObj.permission = result; }); promises.push(permissionPromise); } else { target[operation] = { permission: value }; } }; /** * Build up permissions object and run access functions for each field of an entity * This function is synchronous and collects all async work into the promises array */ export const populateFieldPermissions = ({ id, blockReferencesPermissions, data, fields, operations, parentPermissionsObject, permissionsObject, promises, req })=>{ for (const field of fields){ // Set up permissions for all operations for (const operation of operations){ const parentPermissionForOperation = parentPermissionsObject[operation]?.permission; // Fields don't have all operations of a collection if (operation === 'delete' || operation === 'readVersions' || operation === 'unlock') { continue; } if ('name' in field && field.name) { if (!permissionsObject[field.name]) { permissionsObject[field.name] = {}; } const fieldPermissions = permissionsObject[field.name]; if ('access' in field && field.access && typeof field.access[operation] === 'function') { const accessResult = field.access[operation]({ id, data, doc: data, req }); // Handle both sync and async access results if (isThenable(accessResult)) { const booleanPromise = accessResult.then((result)=>Boolean(result)); setPermission(fieldPermissions, operation, booleanPromise, promises); } else { setPermission(fieldPermissions, operation, Boolean(accessResult), promises); } } else { // Inherit from parent (which might be a promise) setPermission(fieldPermissions, operation, parentPermissionForOperation, promises); } } } // Handle named fields with nested content if ('name' in field && field.name) { const fieldPermissions = permissionsObject[field.name]; if ('fields' in field && field.fields) { if (!fieldPermissions.fields) { fieldPermissions.fields = {}; } populateFieldPermissions({ id, blockReferencesPermissions, data, fields: field.fields, operations, parentPermissionsObject: fieldPermissions, permissionsObject: fieldPermissions.fields, promises, req }); } if ('blocks' in field && field.blocks?.length || 'blockReferences' in field && field.blockReferences?.length) { if (!fieldPermissions.blocks) { fieldPermissions.blocks = {}; } const blocksPermissions = fieldPermissions.blocks; // Set up permissions for all operations for all blocks for (const operation of operations){ // Fields don't have all operations of a collection if (operation === 'delete' || operation === 'readVersions' || operation === 'unlock') { continue; } const parentPermissionForOperation = parentPermissionsObject[operation]?.permission; for (const _block of field.blockReferences ?? field.blocks){ const block = typeof _block === 'string' ? req.payload.blocks[_block] : _block; // Skip if block doesn't exist (invalid block reference) if (!block) { continue; } // Handle block references - check if we've seen this block before if (typeof _block === 'string') { const blockReferencePermissions = blockReferencesPermissions[_block]; if (blockReferencePermissions) { // Reference the cached permissions (may be a promise or resolved object) blocksPermissions[block.slug] = blockReferencePermissions; continue; } } // Initialize block permissions object if needed if (!blocksPermissions[block.slug]) { blocksPermissions[block.slug] = {}; } const blockPermission = blocksPermissions[block.slug]; // Set permission for this operation if (!blockPermission[operation]) { const fieldPermission = fieldPermissions[operation]?.permission ?? parentPermissionForOperation; // Inherit from field permission (which might be a promise) setPermission(blockPermission, operation, fieldPermission, promises); } } } // Process nested content for each unique block (once per block, not once per operation) const processedBlocks = new Set(); for (const _block of field.blockReferences ?? field.blocks){ const block = typeof _block === 'string' ? req.payload.blocks[_block] : _block; // Skip if block doesn't exist (invalid block reference) if (!block || processedBlocks.has(block.slug)) { continue; } processedBlocks.add(block.slug); const blockPermission = blocksPermissions[block.slug]; if (!blockPermission) { continue; } if (!blockPermission.fields) { blockPermission.fields = {}; } // Handle block references with caching - store as promise that will be resolved later if (typeof _block === 'string' && !blockReferencesPermissions[_block]) { // Mark this block as being processed by storing a reference blockReferencesPermissions[_block] = blockPermission; } // Recursively process block fields synchronously populateFieldPermissions({ id, blockReferencesPermissions, data, fields: block.fields, operations, parentPermissionsObject: blockPermission, permissionsObject: blockPermission.fields, promises, req }); } } } // Handle unnamed group fields if ('fields' in field && field.fields && !('name' in field && field.name)) { // Field does not have a name => same parentPermissionsObject populateFieldPermissions({ id, blockReferencesPermissions, data, fields: field.fields, operations, // Field does not have a name here => use parent permissions object parentPermissionsObject, permissionsObject, promises, req }); } // Handle tabs fields if (field.type === 'tabs') { // Process tabs for all operations for (const operation of operations){ // Fields don't have all operations of a collection if (operation === 'delete' || operation === 'readVersions' || operation === 'unlock') { continue; } const parentPermissionForOperation = parentPermissionsObject[operation]?.permission; for (const tab of field.tabs){ if (tabHasName(tab)) { if (!permissionsObject[tab.name]) { permissionsObject[tab.name] = { fields: {} }; } const tabPermissions = permissionsObject[tab.name]; if (!tabPermissions[operation]) { // Inherit from parent (which might be a promise) setPermission(tabPermissions, operation, parentPermissionForOperation, promises); } } } } for (const tab of field.tabs){ if (tabHasName(tab)) { const tabPermissions = permissionsObject[tab.name]; if (!tabPermissions.fields) { tabPermissions.fields = {}; } populateFieldPermissions({ id, blockReferencesPermissions, data, fields: tab.fields, operations, parentPermissionsObject: tabPermissions, permissionsObject: tabPermissions.fields, promises, req }); } else { // Tab does not have a name => same parentPermissionsObject populateFieldPermissions({ id, blockReferencesPermissions, data, fields: tab.fields, operations, // Tab does not have a name here => use parent permissions object parentPermissionsObject, permissionsObject, promises, req }); } } } } }; //# sourceMappingURL=populateFieldPermissions.js.map