payload 3.89.0-internal.95c9cba → 3.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/forms/Field.d.ts +2 -0
- package/dist/admin/forms/Field.d.ts.map +1 -1
- package/dist/admin/forms/Field.js.map +1 -1
- package/dist/admin/forms/Form.d.ts +2 -0
- package/dist/admin/forms/Form.d.ts.map +1 -1
- package/dist/admin/forms/Form.js.map +1 -1
- package/dist/admin/functions/index.d.ts +3 -1
- package/dist/admin/functions/index.d.ts.map +1 -1
- package/dist/admin/functions/index.js.map +1 -1
- package/dist/admin/views/dashboard.d.ts +1 -1
- package/dist/admin/views/dashboard.d.ts.map +1 -1
- package/dist/admin/views/dashboard.js.map +1 -1
- package/dist/auth/applyUserReadAccess.d.ts +15 -0
- package/dist/auth/applyUserReadAccess.d.ts.map +1 -0
- package/dist/auth/applyUserReadAccess.js +41 -0
- package/dist/auth/applyUserReadAccess.js.map +1 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js +20 -0
- package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.js +16 -0
- package/dist/auth/baseFields/apiKey/encryptAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/index.d.ts +6 -0
- package/dist/auth/baseFields/apiKey/index.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/index.js +112 -0
- package/dist/auth/baseFields/apiKey/index.js.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.js +8 -0
- package/dist/auth/baseFields/apiKey/omitAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js +5 -0
- package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js.map +1 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js +5 -0
- package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js.map +1 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts +4 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts.map +1 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js +17 -0
- package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js.map +1 -0
- package/dist/auth/baseFields/auth.d.ts +1 -0
- package/dist/auth/baseFields/auth.d.ts.map +1 -1
- package/dist/auth/baseFields/auth.js +9 -0
- package/dist/auth/baseFields/auth.js.map +1 -1
- package/dist/auth/defaultUnlockAccess.d.ts +3 -0
- package/dist/auth/defaultUnlockAccess.d.ts.map +1 -0
- package/dist/auth/defaultUnlockAccess.js +8 -0
- package/dist/auth/defaultUnlockAccess.js.map +1 -0
- package/dist/auth/endpoints/index.d.ts +1 -0
- package/dist/auth/endpoints/index.d.ts.map +1 -1
- package/dist/auth/endpoints/index.js +8 -0
- package/dist/auth/endpoints/index.js.map +1 -1
- package/dist/auth/endpoints/revealAPIKey.d.ts +7 -0
- package/dist/auth/endpoints/revealAPIKey.d.ts.map +1 -0
- package/dist/auth/endpoints/revealAPIKey.js +120 -0
- package/dist/auth/endpoints/revealAPIKey.js.map +1 -0
- package/dist/auth/executeAccess.d.ts +3 -1
- package/dist/auth/executeAccess.d.ts.map +1 -1
- package/dist/auth/executeAccess.js +2 -1
- package/dist/auth/executeAccess.js.map +1 -1
- package/dist/auth/getAuthFields.d.ts +1 -1
- package/dist/auth/getAuthFields.d.ts.map +1 -1
- package/dist/auth/getAuthFields.js +7 -4
- package/dist/auth/getAuthFields.js.map +1 -1
- package/dist/auth/getFieldsToSign.d.ts.map +1 -1
- package/dist/auth/getFieldsToSign.js +17 -10
- package/dist/auth/getFieldsToSign.js.map +1 -1
- package/dist/auth/getFieldsToSign.spec.js +114 -0
- package/dist/auth/getFieldsToSign.spec.js.map +1 -0
- package/dist/auth/jwt.d.ts.map +1 -1
- package/dist/auth/jwt.js +2 -0
- package/dist/auth/jwt.js.map +1 -1
- package/dist/auth/jwt.spec.js +24 -0
- package/dist/auth/jwt.spec.js.map +1 -0
- package/dist/auth/jwtAuth.d.ts +9 -0
- package/dist/auth/jwtAuth.d.ts.map +1 -0
- package/dist/auth/jwtAuth.js +8 -0
- package/dist/auth/jwtAuth.js.map +1 -0
- package/dist/auth/operations/auth.d.ts.map +1 -1
- package/dist/auth/operations/auth.js +2 -1
- package/dist/auth/operations/auth.js.map +1 -1
- package/dist/auth/operations/forgotPassword.d.ts.map +1 -1
- package/dist/auth/operations/forgotPassword.js +95 -7
- package/dist/auth/operations/forgotPassword.js.map +1 -1
- package/dist/auth/operations/local/auth.js +3 -1
- package/dist/auth/operations/local/auth.js.map +1 -1
- package/dist/auth/operations/login.d.ts.map +1 -1
- package/dist/auth/operations/login.js +48 -26
- package/dist/auth/operations/login.js.map +1 -1
- package/dist/auth/operations/refresh.d.ts.map +1 -1
- package/dist/auth/operations/refresh.js +32 -9
- package/dist/auth/operations/refresh.js.map +1 -1
- package/dist/auth/operations/registerFirstUser.d.ts.map +1 -1
- package/dist/auth/operations/registerFirstUser.js +3 -4
- package/dist/auth/operations/registerFirstUser.js.map +1 -1
- package/dist/auth/operations/resetPassword.d.ts.map +1 -1
- package/dist/auth/operations/resetPassword.js +31 -22
- package/dist/auth/operations/resetPassword.js.map +1 -1
- package/dist/auth/operations/unlock.d.ts.map +1 -1
- package/dist/auth/operations/unlock.js +1 -0
- package/dist/auth/operations/unlock.js.map +1 -1
- package/dist/auth/strategies/apiKey.d.ts.map +1 -1
- package/dist/auth/strategies/apiKey.js +15 -1
- package/dist/auth/strategies/apiKey.js.map +1 -1
- package/dist/auth/strategies/jwt.d.ts.map +1 -1
- package/dist/auth/strategies/jwt.js +61 -10
- package/dist/auth/strategies/jwt.js.map +1 -1
- package/dist/auth/strategies/jwt.spec.js +281 -0
- package/dist/auth/strategies/jwt.spec.js.map +1 -0
- package/dist/auth/strategies/local/authenticate.d.ts +5 -1
- package/dist/auth/strategies/local/authenticate.d.ts.map +1 -1
- package/dist/auth/strategies/local/authenticate.js +8 -3
- package/dist/auth/strategies/local/authenticate.js.map +1 -1
- package/dist/auth/strategies/local/authenticate.spec.js +37 -10
- package/dist/auth/strategies/local/authenticate.spec.js.map +1 -1
- package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts +9 -1
- package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts.map +1 -1
- package/dist/auth/strategies/local/generatePasswordSaltHash.js +61 -34
- package/dist/auth/strategies/local/generatePasswordSaltHash.js.map +1 -1
- package/dist/auth/types.d.ts +16 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/collections/config/client.d.ts +1 -0
- package/dist/collections/config/client.d.ts.map +1 -1
- package/dist/collections/config/client.js +3 -0
- package/dist/collections/config/client.js.map +1 -1
- package/dist/collections/config/defaults.d.ts.map +1 -1
- package/dist/collections/config/defaults.js +4 -2
- package/dist/collections/config/defaults.js.map +1 -1
- package/dist/collections/config/sanitize.d.ts.map +1 -1
- package/dist/collections/config/sanitize.js +17 -6
- package/dist/collections/config/sanitize.js.map +1 -1
- package/dist/collections/endpoints/index.d.ts +1 -0
- package/dist/collections/endpoints/index.d.ts.map +1 -1
- package/dist/collections/endpoints/index.js +6 -5
- package/dist/collections/endpoints/index.js.map +1 -1
- package/dist/collections/operations/create.d.ts.map +1 -1
- package/dist/collections/operations/create.js +105 -8
- package/dist/collections/operations/create.js.map +1 -1
- package/dist/collections/operations/deleteByID.js +13 -13
- package/dist/collections/operations/deleteByID.js.map +1 -1
- package/dist/collections/operations/findDistinct.d.ts.map +1 -1
- package/dist/collections/operations/findDistinct.js +96 -18
- package/dist/collections/operations/findDistinct.js.map +1 -1
- package/dist/collections/operations/restoreVersion.d.ts.map +1 -1
- package/dist/collections/operations/restoreVersion.js +47 -13
- package/dist/collections/operations/restoreVersion.js.map +1 -1
- package/dist/collections/operations/update.d.ts.map +1 -1
- package/dist/collections/operations/update.js +123 -23
- package/dist/collections/operations/update.js.map +1 -1
- package/dist/collections/operations/updateByID.d.ts.map +1 -1
- package/dist/collections/operations/updateByID.js +62 -2
- package/dist/collections/operations/updateByID.js.map +1 -1
- package/dist/collections/operations/utilities/update.d.ts.map +1 -1
- package/dist/collections/operations/utilities/update.js +71 -15
- package/dist/collections/operations/utilities/update.js.map +1 -1
- package/dist/config/orderable/index.d.ts.map +1 -1
- package/dist/config/orderable/index.js +67 -14
- package/dist/config/orderable/index.js.map +1 -1
- package/dist/config/sanitize.d.ts.map +1 -1
- package/dist/config/sanitize.js +1 -1
- package/dist/config/sanitize.js.map +1 -1
- package/dist/config/types.d.ts +13 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/database/getLocalizedPaths.d.ts +2 -1
- package/dist/database/getLocalizedPaths.d.ts.map +1 -1
- package/dist/database/getLocalizedPaths.js +12 -3
- package/dist/database/getLocalizedPaths.js.map +1 -1
- package/dist/database/isNestedRelationshipQuery.d.ts +9 -0
- package/dist/database/isNestedRelationshipQuery.d.ts.map +1 -0
- package/dist/database/isNestedRelationshipQuery.js +14 -0
- package/dist/database/isNestedRelationshipQuery.js.map +1 -0
- package/dist/database/prefixWherePaths.d.ts +6 -0
- package/dist/database/prefixWherePaths.d.ts.map +1 -0
- package/dist/database/prefixWherePaths.js +19 -0
- package/dist/database/prefixWherePaths.js.map +1 -0
- package/dist/database/prefixWherePaths.spec.js +46 -0
- package/dist/database/prefixWherePaths.spec.js.map +1 -0
- package/dist/database/queryValidation/validateQueryPaths.d.ts +2 -1
- package/dist/database/queryValidation/validateQueryPaths.d.ts.map +1 -1
- package/dist/database/queryValidation/validateQueryPaths.js +4 -1
- package/dist/database/queryValidation/validateQueryPaths.js.map +1 -1
- package/dist/database/queryValidation/validateSearchParams.d.ts +2 -1
- package/dist/database/queryValidation/validateSearchParams.d.ts.map +1 -1
- package/dist/database/queryValidation/validateSearchParams.js +82 -4
- package/dist/database/queryValidation/validateSearchParams.js.map +1 -1
- package/dist/database/types.d.ts +5 -0
- package/dist/database/types.d.ts.map +1 -1
- package/dist/database/types.js.map +1 -1
- package/dist/email/normalizeSendEmailOptions.js.map +1 -1
- package/dist/email/types.d.ts +4 -1
- package/dist/email/types.d.ts.map +1 -1
- package/dist/email/types.js.map +1 -1
- package/dist/exports/internal.d.ts +7 -0
- package/dist/exports/internal.d.ts.map +1 -1
- package/dist/exports/internal.js +7 -1
- package/dist/exports/internal.js.map +1 -1
- package/dist/exports/shared.d.ts +3 -0
- package/dist/exports/shared.d.ts.map +1 -1
- package/dist/exports/shared.js +3 -0
- package/dist/exports/shared.js.map +1 -1
- package/dist/fields/config/types.d.ts +7 -5
- package/dist/fields/config/types.d.ts.map +1 -1
- package/dist/fields/config/types.js.map +1 -1
- package/dist/fields/hooks/beforeChange/index.d.ts +7 -1
- package/dist/fields/hooks/beforeChange/index.d.ts.map +1 -1
- package/dist/fields/hooks/beforeChange/index.js +4 -2
- package/dist/fields/hooks/beforeChange/index.js.map +1 -1
- package/dist/fields/hooks/beforeChange/index.spec.js +206 -0
- package/dist/fields/hooks/beforeChange/index.spec.js.map +1 -0
- package/dist/fields/hooks/beforeChange/promise.d.ts +2 -1
- package/dist/fields/hooks/beforeChange/promise.d.ts.map +1 -1
- package/dist/fields/hooks/beforeChange/promise.js +51 -9
- package/dist/fields/hooks/beforeChange/promise.js.map +1 -1
- package/dist/fields/hooks/beforeChange/traverseFields.d.ts +2 -1
- package/dist/fields/hooks/beforeChange/traverseFields.d.ts.map +1 -1
- package/dist/fields/hooks/beforeChange/traverseFields.js +3 -2
- package/dist/fields/hooks/beforeChange/traverseFields.js.map +1 -1
- package/dist/fields/hooks/beforeDuplicate/promise.d.ts.map +1 -1
- package/dist/fields/hooks/beforeDuplicate/promise.js +4 -0
- package/dist/fields/hooks/beforeDuplicate/promise.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts +2 -1
- package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/getFallbackValue.js +2 -2
- package/dist/fields/hooks/beforeValidate/getFallbackValue.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/index.d.ts +6 -1
- package/dist/fields/hooks/beforeValidate/index.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/index.js +3 -1
- package/dist/fields/hooks/beforeValidate/index.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/index.spec.js +90 -0
- package/dist/fields/hooks/beforeValidate/index.spec.js.map +1 -0
- package/dist/fields/hooks/beforeValidate/promise.d.ts +6 -1
- package/dist/fields/hooks/beforeValidate/promise.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/promise.js +23 -8
- package/dist/fields/hooks/beforeValidate/promise.js.map +1 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.d.ts +6 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.d.ts.map +1 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.js +3 -1
- package/dist/fields/hooks/beforeValidate/traverseFields.js.map +1 -1
- package/dist/fields/validations.d.ts.map +1 -1
- package/dist/fields/validations.js +3 -1
- package/dist/fields/validations.js.map +1 -1
- package/dist/globals/operations/restoreVersion.d.ts.map +1 -1
- package/dist/globals/operations/restoreVersion.js +45 -11
- package/dist/globals/operations/restoreVersion.js.map +1 -1
- package/dist/globals/operations/update.d.ts.map +1 -1
- package/dist/globals/operations/update.js +95 -16
- package/dist/globals/operations/update.js.map +1 -1
- package/dist/index.bundled.d.ts +146 -48
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/preferences/config.d.ts.map +1 -1
- package/dist/preferences/config.js +2 -1
- package/dist/preferences/config.js.map +1 -1
- package/dist/query-presets/access.d.ts.map +1 -1
- package/dist/query-presets/access.js +2 -1
- package/dist/query-presets/access.js.map +1 -1
- package/dist/queues/config/collection.d.ts.map +1 -1
- package/dist/queues/config/collection.js +6 -0
- package/dist/queues/config/collection.js.map +1 -1
- package/dist/queues/config/types/index.d.ts +4 -1
- package/dist/queues/config/types/index.d.ts.map +1 -1
- package/dist/queues/config/types/index.js.map +1 -1
- package/dist/queues/endpoints/handleSchedules.d.ts.map +1 -1
- package/dist/queues/endpoints/handleSchedules.js +2 -1
- package/dist/queues/endpoints/handleSchedules.js.map +1 -1
- package/dist/queues/endpoints/run.d.ts.map +1 -1
- package/dist/queues/endpoints/run.js +2 -1
- package/dist/queues/endpoints/run.js.map +1 -1
- package/dist/uploads/assertClientUploadAccess.d.ts +7 -0
- package/dist/uploads/assertClientUploadAccess.d.ts.map +1 -0
- package/dist/uploads/assertClientUploadAccess.js +27 -0
- package/dist/uploads/assertClientUploadAccess.js.map +1 -0
- package/dist/uploads/assertClientUploadAllowed.d.ts +11 -0
- package/dist/uploads/assertClientUploadAllowed.d.ts.map +1 -0
- package/dist/uploads/assertClientUploadAllowed.js +46 -0
- package/dist/uploads/assertClientUploadAllowed.js.map +1 -0
- package/dist/uploads/assertClientUploadAllowed.spec.js +91 -0
- package/dist/uploads/assertClientUploadAllowed.spec.js.map +1 -0
- package/dist/uploads/checkFileRestrictions.d.ts +4 -1
- package/dist/uploads/checkFileRestrictions.d.ts.map +1 -1
- package/dist/uploads/checkFileRestrictions.js +66 -26
- package/dist/uploads/checkFileRestrictions.js.map +1 -1
- package/dist/uploads/clientUploadReceipt.d.ts +23 -0
- package/dist/uploads/clientUploadReceipt.d.ts.map +1 -0
- package/dist/uploads/clientUploadReceipt.js +55 -0
- package/dist/uploads/clientUploadReceipt.js.map +1 -0
- package/dist/uploads/deleteAssociatedFiles.d.ts.map +1 -1
- package/dist/uploads/deleteAssociatedFiles.js +33 -8
- package/dist/uploads/deleteAssociatedFiles.js.map +1 -1
- package/dist/uploads/deleteAssociatedFiles.spec.js +124 -0
- package/dist/uploads/deleteAssociatedFiles.spec.js.map +1 -0
- package/dist/uploads/detectSvgFromXml.d.ts +1 -3
- package/dist/uploads/detectSvgFromXml.d.ts.map +1 -1
- package/dist/uploads/detectSvgFromXml.js +3 -40
- package/dist/uploads/detectSvgFromXml.js.map +1 -1
- package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
- package/dist/uploads/endpoints/getFile.js +5 -3
- package/dist/uploads/endpoints/getFile.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/handlers.d.ts +2 -2
- package/dist/uploads/fetchAPI-multipart/handlers.d.ts.map +1 -1
- package/dist/uploads/fetchAPI-multipart/handlers.js +18 -5
- package/dist/uploads/fetchAPI-multipart/handlers.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/index.d.ts.map +1 -1
- package/dist/uploads/fetchAPI-multipart/index.js +24 -2
- package/dist/uploads/fetchAPI-multipart/index.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts +0 -6
- package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts.map +1 -1
- package/dist/uploads/fetchAPI-multipart/processMultipart.js +146 -126
- package/dist/uploads/fetchAPI-multipart/processMultipart.js.map +1 -1
- package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js +663 -0
- package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js.map +1 -0
- package/dist/uploads/generateFileData.d.ts +3 -1
- package/dist/uploads/generateFileData.d.ts.map +1 -1
- package/dist/uploads/generateFileData.js +29 -16
- package/dist/uploads/generateFileData.js.map +1 -1
- package/dist/uploads/getBaseFields.d.ts.map +1 -1
- package/dist/uploads/getBaseFields.js +3 -1
- package/dist/uploads/getBaseFields.js.map +1 -1
- package/dist/uploads/getExternalFile.d.ts +1 -1
- package/dist/uploads/getExternalFile.d.ts.map +1 -1
- package/dist/uploads/getExternalFile.js +60 -15
- package/dist/uploads/getExternalFile.js.map +1 -1
- package/dist/uploads/getFileFromClientUpload.d.ts +2 -2
- package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -1
- package/dist/uploads/getFileFromClientUpload.js +4 -4
- package/dist/uploads/getFileFromClientUpload.js.map +1 -1
- package/dist/uploads/getFileFromClientUpload.spec.js +62 -0
- package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -1
- package/dist/uploads/getFileTypeFallback.d.ts.map +1 -1
- package/dist/uploads/getFileTypeFallback.js +3 -1
- package/dist/uploads/getFileTypeFallback.js.map +1 -1
- package/dist/uploads/getFileTypeIdentity.d.ts +19 -0
- package/dist/uploads/getFileTypeIdentity.d.ts.map +1 -0
- package/dist/uploads/getFileTypeIdentity.js +34 -0
- package/dist/uploads/getFileTypeIdentity.js.map +1 -0
- package/dist/uploads/getFileTypeIdentity.spec.js +43 -0
- package/dist/uploads/getFileTypeIdentity.spec.js.map +1 -0
- package/dist/uploads/isProcessableImage.d.ts +2 -0
- package/dist/uploads/isProcessableImage.d.ts.map +1 -0
- package/dist/uploads/isProcessableImage.js +7 -0
- package/dist/uploads/isProcessableImage.js.map +1 -0
- package/dist/uploads/safeFetch.d.ts.map +1 -1
- package/dist/uploads/safeFetch.js +3 -2
- package/dist/uploads/safeFetch.js.map +1 -1
- package/dist/uploads/sanitizeUploadData.d.ts +26 -0
- package/dist/uploads/sanitizeUploadData.d.ts.map +1 -0
- package/dist/uploads/sanitizeUploadData.js +159 -0
- package/dist/uploads/sanitizeUploadData.js.map +1 -0
- package/dist/uploads/sanitizeUploadData.spec.js +255 -0
- package/dist/uploads/sanitizeUploadData.spec.js.map +1 -0
- package/dist/uploads/types.d.ts +11 -3
- package/dist/uploads/types.d.ts.map +1 -1
- package/dist/uploads/types.js.map +1 -1
- package/dist/uploads/unlinkClientUploadTempFile.d.ts +20 -0
- package/dist/uploads/unlinkClientUploadTempFile.d.ts.map +1 -0
- package/dist/uploads/unlinkClientUploadTempFile.js +30 -0
- package/dist/uploads/unlinkClientUploadTempFile.js.map +1 -0
- package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
- package/dist/uploads/unlinkTempFiles.js +7 -9
- package/dist/uploads/unlinkTempFiles.js.map +1 -1
- package/dist/uploads/unlinkTempFiles.spec.js +61 -1
- package/dist/uploads/unlinkTempFiles.spec.js.map +1 -1
- package/dist/uploads/uploadContentSecurityPolicy.d.ts +2 -0
- package/dist/uploads/uploadContentSecurityPolicy.d.ts.map +1 -0
- package/dist/uploads/uploadContentSecurityPolicy.js +3 -0
- package/dist/uploads/uploadContentSecurityPolicy.js.map +1 -0
- package/dist/uploads/validateISOBaseMediaFile.d.ts +4 -0
- package/dist/uploads/validateISOBaseMediaFile.d.ts.map +1 -0
- package/dist/uploads/validateISOBaseMediaFile.js +91 -0
- package/dist/uploads/validateISOBaseMediaFile.js.map +1 -0
- package/dist/uploads/validateISOBaseMediaFile.spec.js +130 -0
- package/dist/uploads/validateISOBaseMediaFile.spec.js.map +1 -0
- package/dist/uploads/validateSvg.d.ts +10 -4
- package/dist/uploads/validateSvg.d.ts.map +1 -1
- package/dist/uploads/validateSvg.js +280 -40
- package/dist/uploads/validateSvg.js.map +1 -1
- package/dist/uploads/validateSvg.spec.js +123 -0
- package/dist/uploads/validateSvg.spec.js.map +1 -0
- package/dist/uploads/validateUploadFilename.d.ts +3 -0
- package/dist/uploads/validateUploadFilename.d.ts.map +1 -0
- package/dist/uploads/validateUploadFilename.js +13 -0
- package/dist/uploads/validateUploadFilename.js.map +1 -0
- package/dist/uploads/validateUploadFilename.spec.js +24 -0
- package/dist/uploads/validateUploadFilename.spec.js.map +1 -0
- package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.js +28 -1
- package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.spec.js +158 -0
- package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
- package/dist/utilities/createPayloadRequest.d.ts.map +1 -1
- package/dist/utilities/createPayloadRequest.js +2 -1
- package/dist/utilities/createPayloadRequest.js.map +1 -1
- package/dist/utilities/deepCopyObject.d.ts.map +1 -1
- package/dist/utilities/deepCopyObject.js +110 -19
- package/dist/utilities/deepCopyObject.js.map +1 -1
- package/dist/utilities/deepCopyObject.spec.js +216 -0
- package/dist/utilities/deepCopyObject.spec.js.map +1 -0
- package/dist/utilities/fieldPath.d.ts +13 -0
- package/dist/utilities/fieldPath.d.ts.map +1 -0
- package/dist/utilities/fieldPath.js +51 -0
- package/dist/utilities/fieldPath.js.map +1 -0
- package/dist/utilities/fieldPath.spec.js +155 -0
- package/dist/utilities/fieldPath.spec.js.map +1 -0
- package/dist/utilities/getEntityPermissions/getEntityPermissions.d.ts.map +1 -1
- package/dist/utilities/getEntityPermissions/getEntityPermissions.js +2 -0
- package/dist/utilities/getEntityPermissions/getEntityPermissions.js.map +1 -1
- package/dist/utilities/getObjectDotNotation.d.ts.map +1 -1
- package/dist/utilities/getObjectDotNotation.js +7 -1
- package/dist/utilities/getObjectDotNotation.js.map +1 -1
- package/dist/utilities/getObjectDotNotation.spec.js +33 -0
- package/dist/utilities/getObjectDotNotation.spec.js.map +1 -0
- package/dist/utilities/getSafeRedirect.spec.js +14 -0
- package/dist/utilities/getSafeRedirect.spec.js.map +1 -1
- package/dist/utilities/getTopLevelFieldNames.d.ts +2 -0
- package/dist/utilities/getTopLevelFieldNames.d.ts.map +1 -0
- package/dist/utilities/getTopLevelFieldNames.js +3 -0
- package/dist/utilities/getTopLevelFieldNames.js.map +1 -0
- package/dist/utilities/getTopLevelFieldNames.spec.js +18 -0
- package/dist/utilities/getTopLevelFieldNames.spec.js.map +1 -0
- package/dist/utilities/handleEndpoints.js +1 -1
- package/dist/utilities/handleEndpoints.js.map +1 -1
- package/dist/utilities/parseParams/index.d.ts.map +1 -1
- package/dist/utilities/parseParams/index.js +3 -1
- package/dist/utilities/parseParams/index.js.map +1 -1
- package/dist/utilities/parseParams/index.spec.js +8 -0
- package/dist/utilities/parseParams/index.spec.js.map +1 -1
- package/dist/utilities/traverseFields.d.ts.map +1 -1
- package/dist/utilities/traverseFields.js +5 -14
- package/dist/utilities/traverseFields.js.map +1 -1
- package/dist/utilities/unflatten.d.ts.map +1 -1
- package/dist/utilities/unflatten.js +21 -9
- package/dist/utilities/unflatten.js.map +1 -1
- package/dist/utilities/unflatten.spec.js +38 -0
- package/dist/utilities/unflatten.spec.js.map +1 -0
- package/dist/utilities/wrapInternalEndpoints.d.ts.map +1 -1
- package/dist/utilities/wrapInternalEndpoints.js +10 -3
- package/dist/utilities/wrapInternalEndpoints.js.map +1 -1
- package/dist/utilities/wrapInternalEndpoints.spec.js +124 -0
- package/dist/utilities/wrapInternalEndpoints.spec.js.map +1 -0
- package/dist/versions/allLocalesPublicationStatus.d.ts +38 -0
- package/dist/versions/allLocalesPublicationStatus.d.ts.map +1 -0
- package/dist/versions/allLocalesPublicationStatus.js +57 -0
- package/dist/versions/allLocalesPublicationStatus.js.map +1 -0
- package/dist/versions/getLatestGlobalVersion.d.ts.map +1 -1
- package/dist/versions/getLatestGlobalVersion.js +10 -2
- package/dist/versions/getLatestGlobalVersion.js.map +1 -1
- package/dist/versions/getRestoredStatusesToAuthorize.d.ts +9 -0
- package/dist/versions/getRestoredStatusesToAuthorize.d.ts.map +1 -0
- package/dist/versions/getRestoredStatusesToAuthorize.js +22 -0
- package/dist/versions/getRestoredStatusesToAuthorize.js.map +1 -0
- package/dist/versions/getRestoredStatusesToAuthorize.spec.js +50 -0
- package/dist/versions/getRestoredStatusesToAuthorize.spec.js.map +1 -0
- package/dist/versions/schedule/job.d.ts +2 -2
- package/dist/versions/schedule/job.d.ts.map +1 -1
- package/dist/versions/schedule/job.js +11 -7
- package/dist/versions/schedule/job.js.map +1 -1
- package/dist/versions/schedule/types.d.ts +5 -1
- package/dist/versions/schedule/types.d.ts.map +1 -1
- package/dist/versions/schedule/types.js.map +1 -1
- package/package.json +4 -2
|
@@ -3,9 +3,11 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import { ValidationError } from '../errors/index.js';
|
|
4
4
|
import { validateMimeType } from '../utilities/validateMimeType.js';
|
|
5
5
|
import { validatePDF } from '../utilities/validatePDF.js';
|
|
6
|
-
import { detectSvgFromXml } from './detectSvgFromXml.js';
|
|
7
6
|
import { getFileTypeFallback } from './getFileTypeFallback.js';
|
|
8
|
-
import {
|
|
7
|
+
import { getFileExtension, getMimeTypeEssence, getSanitizedUploadFilename, isSvgUpload, isXmlUpload } from './getFileTypeIdentity.js';
|
|
8
|
+
import { isProcessableImage } from './isProcessableImage.js';
|
|
9
|
+
import { isISOBaseMediaMimeType, validateISOBaseMediaBuffer, validateISOBaseMediaFile } from './validateISOBaseMediaFile.js';
|
|
10
|
+
import { inspectSvg, inspectSvgFile } from './validateSvg.js';
|
|
9
11
|
/**
|
|
10
12
|
* Restricted file types and their extensions.
|
|
11
13
|
*/ export const RESTRICTED_FILE_EXT_AND_TYPES = [
|
|
@@ -111,6 +113,10 @@ import { validateSvg } from './validateSvg.js';
|
|
|
111
113
|
],
|
|
112
114
|
mimeType: 'text/html'
|
|
113
115
|
},
|
|
116
|
+
{
|
|
117
|
+
extensions: [],
|
|
118
|
+
mimeType: 'application/xhtml+xml'
|
|
119
|
+
},
|
|
114
120
|
{
|
|
115
121
|
extensions: [
|
|
116
122
|
'php',
|
|
@@ -236,16 +242,12 @@ export const checkFileRestrictions = async ({ collection, file, req })=>{
|
|
|
236
242
|
}
|
|
237
243
|
return mimeType.startsWith('image/') || mimeType.startsWith('video/') || mimeType.startsWith('audio/') || mimeType === 'application/pdf';
|
|
238
244
|
};
|
|
239
|
-
// Skip validation if `allowRestrictedFileTypes` is true
|
|
240
|
-
if (allowRestrictedFileTypes) {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
245
|
// For temp files, use fileTypeFromFile so large files (e.g. video) are never loaded into memory
|
|
244
|
-
// just for detection.
|
|
245
|
-
//
|
|
246
|
+
// just for detection. Content validation streams SVG/XML temp files and only loads a full
|
|
247
|
+
// temp-file buffer for PDF integrity checks.
|
|
246
248
|
const { tempFilePath } = file;
|
|
247
249
|
const isTempFile = !!tempFilePath && (!file.data || file.data.length === 0);
|
|
248
|
-
// Lazily reads the full file — only reached for
|
|
250
|
+
// Lazily reads the full file — only reached for PDF validation.
|
|
249
251
|
let _fileBuffer;
|
|
250
252
|
const getFileBuffer = async ()=>{
|
|
251
253
|
if (_fileBuffer) {
|
|
@@ -268,12 +270,21 @@ export const checkFileRestrictions = async ({ collection, file, req })=>{
|
|
|
268
270
|
});
|
|
269
271
|
}
|
|
270
272
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
const typeFromExtension = getFileExtension(getSanitizedUploadFilename(file.name));
|
|
274
|
+
let isSvg = isSvgUpload({
|
|
275
|
+
filename: file.name,
|
|
276
|
+
mimeType: file.mimetype
|
|
277
|
+
});
|
|
278
|
+
let svgInspection;
|
|
279
|
+
const getSvgInspection = async ()=>{
|
|
280
|
+
svgInspection ??= isTempFile && tempFilePath ? await inspectSvgFile(tempFilePath) : inspectSvg(file.data);
|
|
281
|
+
return svgInspection;
|
|
282
|
+
};
|
|
283
|
+
let detected;
|
|
284
|
+
try {
|
|
285
|
+
detected = isTempFile && tempFilePath ? await fileTypeFromFile(tempFilePath) : await fileTypeFromBuffer(file.data);
|
|
286
|
+
} catch {
|
|
287
|
+
if (configMimeTypes.length > 0) {
|
|
277
288
|
throw new ValidationError({
|
|
278
289
|
errors: [
|
|
279
290
|
{
|
|
@@ -283,10 +294,38 @@ export const checkFileRestrictions = async ({ collection, file, req })=>{
|
|
|
283
294
|
]
|
|
284
295
|
});
|
|
285
296
|
}
|
|
286
|
-
|
|
297
|
+
}
|
|
298
|
+
if (isSvg || isXmlUpload({
|
|
299
|
+
filename: file.name,
|
|
300
|
+
mimeType: file.mimetype
|
|
301
|
+
}) || detected?.mime === 'application/xml' || !detected && isProcessableImage(file.mimetype)) {
|
|
302
|
+
const inspection = await getSvgInspection();
|
|
303
|
+
isSvg = inspection.isSvg;
|
|
304
|
+
if (inspection.isSvg) {
|
|
305
|
+
detected = {
|
|
306
|
+
ext: 'svg',
|
|
307
|
+
mime: 'image/svg+xml'
|
|
308
|
+
};
|
|
309
|
+
} else if (inspection.isValid && !detected) {
|
|
310
|
+
detected = {
|
|
311
|
+
ext: 'xml',
|
|
312
|
+
mime: 'application/xml'
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (detected && isISOBaseMediaMimeType(detected.mime)) {
|
|
317
|
+
const isValidISOBaseMedia = isTempFile && tempFilePath ? await validateISOBaseMediaFile(tempFilePath) : await validateISOBaseMediaBuffer(file.data);
|
|
318
|
+
if (!isValidISOBaseMedia) {
|
|
319
|
+
errors.push('Invalid or corrupted ISO base media file.');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (allowRestrictedFileTypes && errors.length === 0) {
|
|
323
|
+
return detected;
|
|
324
|
+
}
|
|
325
|
+
// Secondary mimetype check to assess file type from buffer
|
|
326
|
+
if (configMimeTypes.length > 0) {
|
|
287
327
|
// Handle SVG files that are detected as XML due to <?xml declarations
|
|
288
328
|
if (detected?.mime === 'application/xml' && configMimeTypes.some((type)=>type.includes('image/') && (type.includes('svg') || type === 'image/*'))) {
|
|
289
|
-
const isSvg = detectSvgFromXml(await getFileBuffer());
|
|
290
329
|
if (isSvg) {
|
|
291
330
|
detected = {
|
|
292
331
|
ext: 'svg',
|
|
@@ -300,13 +339,6 @@ export const checkFileRestrictions = async ({ collection, file, req })=>{
|
|
|
300
339
|
if (!extIsValid) {
|
|
301
340
|
errors.push(`File type ${mimeTypeFromExtension} (from extension ${typeFromExtension}) is not allowed.`);
|
|
302
341
|
} else {
|
|
303
|
-
// SVG security check (text-based files not detectable by buffer)
|
|
304
|
-
if (typeFromExtension.toLowerCase() === 'svg') {
|
|
305
|
-
const isSafeSvg = validateSvg(await getFileBuffer());
|
|
306
|
-
if (!isSafeSvg) {
|
|
307
|
-
errors.push('SVG file contains potentially harmful content.');
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
342
|
// PDF validation
|
|
311
343
|
if (mimeTypeFromExtension === 'application/pdf') {
|
|
312
344
|
const isValidPDF = validatePDF(await getFileBuffer());
|
|
@@ -330,15 +362,22 @@ export const checkFileRestrictions = async ({ collection, file, req })=>{
|
|
|
330
362
|
errors.push(`Invalid MIME type: ${detected.mime}.`);
|
|
331
363
|
}
|
|
332
364
|
} else {
|
|
365
|
+
const mimeTypeEssence = getMimeTypeEssence(file.mimetype);
|
|
333
366
|
const isRestricted = RESTRICTED_FILE_EXT_AND_TYPES.some((type)=>{
|
|
334
|
-
const hasRestrictedExt = type.extensions.
|
|
335
|
-
const hasRestrictedMime = type.mimeType ===
|
|
367
|
+
const hasRestrictedExt = type.extensions.includes(typeFromExtension.toLowerCase());
|
|
368
|
+
const hasRestrictedMime = type.mimeType === mimeTypeEssence;
|
|
336
369
|
return hasRestrictedExt || hasRestrictedMime;
|
|
337
370
|
});
|
|
338
371
|
if (isRestricted) {
|
|
339
372
|
errors.push(`File type '${file.mimetype}' not allowed ${file.name}: Restricted file type detected -- set 'allowRestrictedFileTypes' to true to skip this check for this Collection.`);
|
|
340
373
|
}
|
|
341
374
|
}
|
|
375
|
+
if (isSvg) {
|
|
376
|
+
const isSafeSvg = (await getSvgInspection()).isSafe;
|
|
377
|
+
if (!isSafeSvg) {
|
|
378
|
+
errors.push('SVG file contains potentially harmful content.');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
342
381
|
if (errors.length > 0) {
|
|
343
382
|
req.payload.logger.error(errors.join(', '));
|
|
344
383
|
throw new ValidationError({
|
|
@@ -350,6 +389,7 @@ export const checkFileRestrictions = async ({ collection, file, req })=>{
|
|
|
350
389
|
]
|
|
351
390
|
});
|
|
352
391
|
}
|
|
392
|
+
return detected;
|
|
353
393
|
};
|
|
354
394
|
|
|
355
395
|
//# sourceMappingURL=checkFileRestrictions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/uploads/checkFileRestrictions.ts"],"sourcesContent":["import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type'\nimport fs from 'fs/promises'\n\nimport type { checkFileRestrictionsParams, FileAllowList } from './types.js'\n\nimport { ValidationError } from '../errors/index.js'\nimport { validateMimeType } from '../utilities/validateMimeType.js'\nimport { validatePDF } from '../utilities/validatePDF.js'\nimport { detectSvgFromXml } from './detectSvgFromXml.js'\nimport { getFileTypeFallback } from './getFileTypeFallback.js'\nimport { validateSvg } from './validateSvg.js'\n\n/**\n * Restricted file types and their extensions.\n */\nexport const RESTRICTED_FILE_EXT_AND_TYPES: FileAllowList = [\n { extensions: ['exe', 'dll'], mimeType: 'application/x-msdownload' },\n { extensions: ['exe', 'com', 'app', 'action'], mimeType: 'application/x-executable' },\n { extensions: ['bat', 'cmd'], mimeType: 'application/x-msdos-program' },\n { extensions: ['exe', 'com'], mimeType: 'application/x-ms-dos-executable' },\n { extensions: ['dmg'], mimeType: 'application/x-apple-diskimage' },\n { extensions: ['deb'], mimeType: 'application/x-debian-package' },\n { extensions: ['rpm'], mimeType: 'application/x-redhat-package-manager' },\n { extensions: ['exe', 'dll'], mimeType: 'application/vnd.microsoft.portable-executable' },\n { extensions: ['msi'], mimeType: 'application/x-msi' },\n { extensions: ['jar', 'ear', 'war'], mimeType: 'application/java-archive' },\n { extensions: ['desktop'], mimeType: 'application/x-desktop' },\n { extensions: ['cpl'], mimeType: 'application/x-cpl' },\n { extensions: ['lnk'], mimeType: 'application/x-ms-shortcut' },\n { extensions: ['pkg'], mimeType: 'application/x-apple-installer' },\n { extensions: ['htm', 'html', 'shtml', 'xhtml'], mimeType: 'text/html' },\n { extensions: ['php', 'phtml'], mimeType: 'application/x-httpd-php' },\n { extensions: ['js', 'jse'], mimeType: 'text/javascript' },\n { extensions: ['jsp'], mimeType: 'application/x-jsp' },\n { extensions: ['py'], mimeType: 'text/x-python' },\n { extensions: ['rb'], mimeType: 'text/x-ruby' },\n { extensions: ['pl'], mimeType: 'text/x-perl' },\n { extensions: ['ps1', 'psc1', 'psd1', 'psh', 'psm1'], mimeType: 'application/x-powershell' },\n { extensions: ['vbe', 'vbs'], mimeType: 'application/x-vbscript' },\n { extensions: ['ws', 'wsc', 'wsf', 'wsh'], mimeType: 'application/x-ms-wsh' },\n { extensions: ['scr'], mimeType: 'application/x-msdownload' },\n { extensions: ['asp', 'aspx'], mimeType: 'application/x-asp' },\n { extensions: ['hta'], mimeType: 'application/x-hta' },\n { extensions: ['reg'], mimeType: 'application/x-registry' },\n { extensions: ['url'], mimeType: 'application/x-url' },\n { extensions: ['workflow'], mimeType: 'application/x-workflow' },\n { extensions: ['command'], mimeType: 'application/x-command' },\n]\n\nexport const checkFileRestrictions = async ({\n collection,\n file,\n req,\n}: checkFileRestrictionsParams): Promise<void> => {\n const errors: string[] = []\n const { upload: uploadConfig } = collection\n const configMimeTypes =\n uploadConfig &&\n typeof uploadConfig === 'object' &&\n 'mimeTypes' in uploadConfig &&\n Array.isArray(uploadConfig.mimeTypes)\n ? uploadConfig.mimeTypes\n : []\n\n const allowRestrictedFileTypes =\n uploadConfig && typeof uploadConfig === 'object' && 'allowRestrictedFileTypes' in uploadConfig\n ? (uploadConfig as { allowRestrictedFileTypes?: boolean }).allowRestrictedFileTypes\n : false\n\n const expectsDetectableType = (mimeType: string): boolean => {\n const textBasedTypes = ['/svg', 'image/svg+xml', 'image/x-xbitmap', 'image/x-xpixmap']\n\n if (textBasedTypes.includes(mimeType)) {\n return false\n }\n\n return (\n mimeType.startsWith('image/') ||\n mimeType.startsWith('video/') ||\n mimeType.startsWith('audio/') ||\n mimeType === 'application/pdf'\n )\n }\n\n // Skip validation if `allowRestrictedFileTypes` is true\n if (allowRestrictedFileTypes) {\n return\n }\n\n // For temp files, use fileTypeFromFile so large files (e.g. video) are never loaded into memory\n // just for detection. For content validation (SVG safety, PDF integrity), the full buffer is\n // loaded lazily and only when the file type actually requires it.\n const { tempFilePath } = file\n const isTempFile = !!tempFilePath && (!file.data || file.data.length === 0)\n\n // Lazily reads the full file — only reached for small text-based types (SVG, PDF).\n let _fileBuffer: Buffer | undefined\n const getFileBuffer = async (): Promise<Buffer> => {\n if (_fileBuffer) {\n return _fileBuffer\n }\n if (!isTempFile || !tempFilePath) {\n return (_fileBuffer = file.data)\n }\n try {\n _fileBuffer = await fs.readFile(tempFilePath)\n return _fileBuffer\n } catch {\n throw new ValidationError({\n errors: [{ message: 'Could not read uploaded file for validation.', path: 'file' }],\n })\n }\n }\n\n // Secondary mimetype check to assess file type from buffer\n if (configMimeTypes.length > 0) {\n let detected\n try {\n detected =\n isTempFile && tempFilePath\n ? await fileTypeFromFile(tempFilePath)\n : await fileTypeFromBuffer(file.data)\n } catch {\n throw new ValidationError({\n errors: [{ message: 'Could not read uploaded file for type detection.', path: 'file' }],\n })\n }\n const typeFromExtension = file.name.split('.').pop() || ''\n\n // Handle SVG files that are detected as XML due to <?xml declarations\n if (\n detected?.mime === 'application/xml' &&\n configMimeTypes.some(\n (type) => type.includes('image/') && (type.includes('svg') || type === 'image/*'),\n )\n ) {\n const isSvg = detectSvgFromXml(await getFileBuffer())\n if (isSvg) {\n detected = { ext: 'svg', mime: 'image/svg+xml' }\n }\n }\n\n if (!detected) {\n const mimeTypeFromExtension = getFileTypeFallback(file.name).mime\n const extIsValid = validateMimeType(mimeTypeFromExtension, configMimeTypes)\n\n if (!extIsValid) {\n errors.push(\n `File type ${mimeTypeFromExtension} (from extension ${typeFromExtension}) is not allowed.`,\n )\n } else {\n // SVG security check (text-based files not detectable by buffer)\n if (typeFromExtension.toLowerCase() === 'svg') {\n const isSafeSvg = validateSvg(await getFileBuffer())\n if (!isSafeSvg) {\n errors.push('SVG file contains potentially harmful content.')\n }\n }\n\n // PDF validation\n if (mimeTypeFromExtension === 'application/pdf') {\n const isValidPDF = validatePDF(await getFileBuffer())\n if (!isValidPDF) {\n errors.push('Invalid or corrupted PDF file.')\n }\n }\n }\n\n if (expectsDetectableType(mimeTypeFromExtension)) {\n req.payload.logger.warn(\n `File buffer returned no detectable MIME type for ${file.name}. Falling back to extension-based validation.`,\n )\n }\n }\n\n const passesMimeTypeCheck = detected?.mime && validateMimeType(detected.mime, configMimeTypes)\n\n if (passesMimeTypeCheck && detected?.mime === 'application/pdf') {\n const isValidPDF = validatePDF(await getFileBuffer())\n if (!isValidPDF) {\n errors.push('Invalid PDF file.')\n }\n }\n\n if (detected && !passesMimeTypeCheck) {\n errors.push(`Invalid MIME type: ${detected.mime}.`)\n }\n } else {\n const isRestricted = RESTRICTED_FILE_EXT_AND_TYPES.some((type) => {\n const hasRestrictedExt = type.extensions.some((ext) => file.name.toLowerCase().endsWith(ext))\n const hasRestrictedMime = type.mimeType === file.mimetype\n return hasRestrictedExt || hasRestrictedMime\n })\n if (isRestricted) {\n errors.push(\n `File type '${file.mimetype}' not allowed ${file.name}: Restricted file type detected -- set 'allowRestrictedFileTypes' to true to skip this check for this Collection.`,\n )\n }\n }\n\n if (errors.length > 0) {\n req.payload.logger.error(errors.join(', '))\n throw new ValidationError({\n errors: [{ message: errors.join(', '), path: 'file' }],\n })\n }\n}\n"],"names":["fileTypeFromBuffer","fileTypeFromFile","fs","ValidationError","validateMimeType","validatePDF","detectSvgFromXml","getFileTypeFallback","validateSvg","RESTRICTED_FILE_EXT_AND_TYPES","extensions","mimeType","checkFileRestrictions","collection","file","req","errors","upload","uploadConfig","configMimeTypes","Array","isArray","mimeTypes","allowRestrictedFileTypes","expectsDetectableType","textBasedTypes","includes","startsWith","tempFilePath","isTempFile","data","length","_fileBuffer","getFileBuffer","readFile","message","path","detected","typeFromExtension","name","split","pop","mime","some","type","isSvg","ext","mimeTypeFromExtension","extIsValid","push","toLowerCase","isSafeSvg","isValidPDF","payload","logger","warn","passesMimeTypeCheck","isRestricted","hasRestrictedExt","endsWith","hasRestrictedMime","mimetype","error","join"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/checkFileRestrictions.ts"],"sourcesContent":["import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type'\nimport fs from 'fs/promises'\n\nimport type { checkFileRestrictionsParams, FileAllowList } from './types.js'\n\nimport { ValidationError } from '../errors/index.js'\nimport { validateMimeType } from '../utilities/validateMimeType.js'\nimport { validatePDF } from '../utilities/validatePDF.js'\nimport { getFileTypeFallback } from './getFileTypeFallback.js'\nimport {\n getFileExtension,\n getMimeTypeEssence,\n getSanitizedUploadFilename,\n isSvgUpload,\n isXmlUpload,\n} from './getFileTypeIdentity.js'\nimport { isProcessableImage } from './isProcessableImage.js'\nimport {\n isISOBaseMediaMimeType,\n validateISOBaseMediaBuffer,\n validateISOBaseMediaFile,\n} from './validateISOBaseMediaFile.js'\nimport { inspectSvg, inspectSvgFile } from './validateSvg.js'\n\n/**\n * Restricted file types and their extensions.\n */\nexport const RESTRICTED_FILE_EXT_AND_TYPES: FileAllowList = [\n { extensions: ['exe', 'dll'], mimeType: 'application/x-msdownload' },\n { extensions: ['exe', 'com', 'app', 'action'], mimeType: 'application/x-executable' },\n { extensions: ['bat', 'cmd'], mimeType: 'application/x-msdos-program' },\n { extensions: ['exe', 'com'], mimeType: 'application/x-ms-dos-executable' },\n { extensions: ['dmg'], mimeType: 'application/x-apple-diskimage' },\n { extensions: ['deb'], mimeType: 'application/x-debian-package' },\n { extensions: ['rpm'], mimeType: 'application/x-redhat-package-manager' },\n { extensions: ['exe', 'dll'], mimeType: 'application/vnd.microsoft.portable-executable' },\n { extensions: ['msi'], mimeType: 'application/x-msi' },\n { extensions: ['jar', 'ear', 'war'], mimeType: 'application/java-archive' },\n { extensions: ['desktop'], mimeType: 'application/x-desktop' },\n { extensions: ['cpl'], mimeType: 'application/x-cpl' },\n { extensions: ['lnk'], mimeType: 'application/x-ms-shortcut' },\n { extensions: ['pkg'], mimeType: 'application/x-apple-installer' },\n { extensions: ['htm', 'html', 'shtml', 'xhtml'], mimeType: 'text/html' },\n { extensions: [], mimeType: 'application/xhtml+xml' },\n { extensions: ['php', 'phtml'], mimeType: 'application/x-httpd-php' },\n { extensions: ['js', 'jse'], mimeType: 'text/javascript' },\n { extensions: ['jsp'], mimeType: 'application/x-jsp' },\n { extensions: ['py'], mimeType: 'text/x-python' },\n { extensions: ['rb'], mimeType: 'text/x-ruby' },\n { extensions: ['pl'], mimeType: 'text/x-perl' },\n { extensions: ['ps1', 'psc1', 'psd1', 'psh', 'psm1'], mimeType: 'application/x-powershell' },\n { extensions: ['vbe', 'vbs'], mimeType: 'application/x-vbscript' },\n { extensions: ['ws', 'wsc', 'wsf', 'wsh'], mimeType: 'application/x-ms-wsh' },\n { extensions: ['scr'], mimeType: 'application/x-msdownload' },\n { extensions: ['asp', 'aspx'], mimeType: 'application/x-asp' },\n { extensions: ['hta'], mimeType: 'application/x-hta' },\n { extensions: ['reg'], mimeType: 'application/x-registry' },\n { extensions: ['url'], mimeType: 'application/x-url' },\n { extensions: ['workflow'], mimeType: 'application/x-workflow' },\n { extensions: ['command'], mimeType: 'application/x-command' },\n]\n\nexport const checkFileRestrictions = async ({\n collection,\n file,\n req,\n}: checkFileRestrictionsParams): Promise<{ ext: string; mime: string } | undefined> => {\n const errors: string[] = []\n const { upload: uploadConfig } = collection\n const configMimeTypes =\n uploadConfig &&\n typeof uploadConfig === 'object' &&\n 'mimeTypes' in uploadConfig &&\n Array.isArray(uploadConfig.mimeTypes)\n ? uploadConfig.mimeTypes\n : []\n\n const allowRestrictedFileTypes =\n uploadConfig && typeof uploadConfig === 'object' && 'allowRestrictedFileTypes' in uploadConfig\n ? (uploadConfig as { allowRestrictedFileTypes?: boolean }).allowRestrictedFileTypes\n : false\n\n const expectsDetectableType = (mimeType: string): boolean => {\n const textBasedTypes = ['/svg', 'image/svg+xml', 'image/x-xbitmap', 'image/x-xpixmap']\n\n if (textBasedTypes.includes(mimeType)) {\n return false\n }\n\n return (\n mimeType.startsWith('image/') ||\n mimeType.startsWith('video/') ||\n mimeType.startsWith('audio/') ||\n mimeType === 'application/pdf'\n )\n }\n\n // For temp files, use fileTypeFromFile so large files (e.g. video) are never loaded into memory\n // just for detection. Content validation streams SVG/XML temp files and only loads a full\n // temp-file buffer for PDF integrity checks.\n const { tempFilePath } = file\n const isTempFile = !!tempFilePath && (!file.data || file.data.length === 0)\n\n // Lazily reads the full file — only reached for PDF validation.\n let _fileBuffer: Buffer | undefined\n const getFileBuffer = async (): Promise<Buffer> => {\n if (_fileBuffer) {\n return _fileBuffer\n }\n if (!isTempFile || !tempFilePath) {\n return (_fileBuffer = file.data)\n }\n try {\n _fileBuffer = await fs.readFile(tempFilePath)\n return _fileBuffer\n } catch {\n throw new ValidationError({\n errors: [{ message: 'Could not read uploaded file for validation.', path: 'file' }],\n })\n }\n }\n\n const typeFromExtension = getFileExtension(getSanitizedUploadFilename(file.name))\n let isSvg = isSvgUpload({ filename: file.name, mimeType: file.mimetype })\n let svgInspection: Awaited<ReturnType<typeof inspectSvgFile>> | undefined\n\n const getSvgInspection = async () => {\n svgInspection ??=\n isTempFile && tempFilePath ? await inspectSvgFile(tempFilePath) : inspectSvg(file.data)\n return svgInspection\n }\n\n let detected\n try {\n detected =\n isTempFile && tempFilePath\n ? await fileTypeFromFile(tempFilePath)\n : await fileTypeFromBuffer(file.data)\n } catch {\n if (configMimeTypes.length > 0) {\n throw new ValidationError({\n errors: [{ message: 'Could not read uploaded file for type detection.', path: 'file' }],\n })\n }\n }\n\n if (\n isSvg ||\n isXmlUpload({ filename: file.name, mimeType: file.mimetype }) ||\n detected?.mime === 'application/xml' ||\n (!detected && isProcessableImage(file.mimetype))\n ) {\n const inspection = await getSvgInspection()\n isSvg = inspection.isSvg\n if (inspection.isSvg) {\n detected = { ext: 'svg', mime: 'image/svg+xml' }\n } else if (inspection.isValid && !detected) {\n detected = { ext: 'xml', mime: 'application/xml' }\n }\n }\n\n if (detected && isISOBaseMediaMimeType(detected.mime)) {\n const isValidISOBaseMedia =\n isTempFile && tempFilePath\n ? await validateISOBaseMediaFile(tempFilePath)\n : await validateISOBaseMediaBuffer(file.data)\n\n if (!isValidISOBaseMedia) {\n errors.push('Invalid or corrupted ISO base media file.')\n }\n }\n\n if (allowRestrictedFileTypes && errors.length === 0) {\n return detected\n }\n\n // Secondary mimetype check to assess file type from buffer\n if (configMimeTypes.length > 0) {\n // Handle SVG files that are detected as XML due to <?xml declarations\n if (\n detected?.mime === 'application/xml' &&\n configMimeTypes.some(\n (type) => type.includes('image/') && (type.includes('svg') || type === 'image/*'),\n )\n ) {\n if (isSvg) {\n detected = { ext: 'svg', mime: 'image/svg+xml' }\n }\n }\n\n if (!detected) {\n const mimeTypeFromExtension = getFileTypeFallback(file.name).mime\n const extIsValid = validateMimeType(mimeTypeFromExtension, configMimeTypes)\n\n if (!extIsValid) {\n errors.push(\n `File type ${mimeTypeFromExtension} (from extension ${typeFromExtension}) is not allowed.`,\n )\n } else {\n // PDF validation\n if (mimeTypeFromExtension === 'application/pdf') {\n const isValidPDF = validatePDF(await getFileBuffer())\n if (!isValidPDF) {\n errors.push('Invalid or corrupted PDF file.')\n }\n }\n }\n\n if (expectsDetectableType(mimeTypeFromExtension)) {\n req.payload.logger.warn(\n `File buffer returned no detectable MIME type for ${file.name}. Falling back to extension-based validation.`,\n )\n }\n }\n\n const passesMimeTypeCheck = detected?.mime && validateMimeType(detected.mime, configMimeTypes)\n\n if (passesMimeTypeCheck && detected?.mime === 'application/pdf') {\n const isValidPDF = validatePDF(await getFileBuffer())\n if (!isValidPDF) {\n errors.push('Invalid PDF file.')\n }\n }\n\n if (detected && !passesMimeTypeCheck) {\n errors.push(`Invalid MIME type: ${detected.mime}.`)\n }\n } else {\n const mimeTypeEssence = getMimeTypeEssence(file.mimetype)\n const isRestricted = RESTRICTED_FILE_EXT_AND_TYPES.some((type) => {\n const hasRestrictedExt = type.extensions.includes(typeFromExtension.toLowerCase())\n const hasRestrictedMime = type.mimeType === mimeTypeEssence\n return hasRestrictedExt || hasRestrictedMime\n })\n if (isRestricted) {\n errors.push(\n `File type '${file.mimetype}' not allowed ${file.name}: Restricted file type detected -- set 'allowRestrictedFileTypes' to true to skip this check for this Collection.`,\n )\n }\n }\n\n if (isSvg) {\n const isSafeSvg = (await getSvgInspection()).isSafe\n if (!isSafeSvg) {\n errors.push('SVG file contains potentially harmful content.')\n }\n }\n\n if (errors.length > 0) {\n req.payload.logger.error(errors.join(', '))\n throw new ValidationError({\n errors: [{ message: errors.join(', '), path: 'file' }],\n })\n }\n\n return detected\n}\n"],"names":["fileTypeFromBuffer","fileTypeFromFile","fs","ValidationError","validateMimeType","validatePDF","getFileTypeFallback","getFileExtension","getMimeTypeEssence","getSanitizedUploadFilename","isSvgUpload","isXmlUpload","isProcessableImage","isISOBaseMediaMimeType","validateISOBaseMediaBuffer","validateISOBaseMediaFile","inspectSvg","inspectSvgFile","RESTRICTED_FILE_EXT_AND_TYPES","extensions","mimeType","checkFileRestrictions","collection","file","req","errors","upload","uploadConfig","configMimeTypes","Array","isArray","mimeTypes","allowRestrictedFileTypes","expectsDetectableType","textBasedTypes","includes","startsWith","tempFilePath","isTempFile","data","length","_fileBuffer","getFileBuffer","readFile","message","path","typeFromExtension","name","isSvg","filename","mimetype","svgInspection","getSvgInspection","detected","mime","inspection","ext","isValid","isValidISOBaseMedia","push","some","type","mimeTypeFromExtension","extIsValid","isValidPDF","payload","logger","warn","passesMimeTypeCheck","mimeTypeEssence","isRestricted","hasRestrictedExt","toLowerCase","hasRestrictedMime","isSafeSvg","isSafe","error","join"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PayloadRequest } from '../types/index.js';
|
|
2
|
+
export type ClientUploadReceipt = {
|
|
3
|
+
collectionSlug: string;
|
|
4
|
+
context: Record<string, unknown>;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
filename: string;
|
|
7
|
+
userCollection: null | string;
|
|
8
|
+
userID: null | number | string;
|
|
9
|
+
};
|
|
10
|
+
export type SignedClientUploadReceipt = `${string}.${string}`;
|
|
11
|
+
export declare const createClientUploadReceipt: ({ collectionSlug, context, filename, req, user, }: {
|
|
12
|
+
collectionSlug: string;
|
|
13
|
+
context: Record<string, unknown>;
|
|
14
|
+
filename: string;
|
|
15
|
+
req: PayloadRequest;
|
|
16
|
+
user?: Pick<ClientUploadReceipt, "userCollection" | "userID">;
|
|
17
|
+
}) => SignedClientUploadReceipt;
|
|
18
|
+
export declare const verifyClientUploadReceipt: ({ collectionSlug, req, signedReceipt, }: {
|
|
19
|
+
collectionSlug: string;
|
|
20
|
+
req: PayloadRequest;
|
|
21
|
+
signedReceipt: string;
|
|
22
|
+
}) => ClientUploadReceipt;
|
|
23
|
+
//# sourceMappingURL=clientUploadReceipt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientUploadReceipt.d.ts","sourceRoot":"","sources":["../../src/uploads/clientUploadReceipt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAIvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,IAAI,GAAG,MAAM,CAAA;IAC7B,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;CAC/B,CAAA;AAGD,MAAM,MAAM,yBAAyB,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAA;AAK7D,eAAO,MAAM,yBAAyB,sDAMnC;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,IAAI,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,gBAAgB,GAAG,QAAQ,CAAC,CAAA;CAC9D,KAAG,yBAWH,CAAA;AAED,eAAO,MAAM,yBAAyB,4CAInC;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,GAAG,EAAE,cAAc,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;CACtB,KAAG,mBA2BH,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { APIError } from '../errors/APIError.js';
|
|
3
|
+
const LIFETIME_MS = 6 * 60 * 60 * 1000;
|
|
4
|
+
const DOMAIN = 'payload-client-upload-receipt-v1';
|
|
5
|
+
export const createClientUploadReceipt = ({ collectionSlug, context, filename, req, user = getUser(req) })=>{
|
|
6
|
+
const data = {
|
|
7
|
+
collectionSlug,
|
|
8
|
+
context,
|
|
9
|
+
expiresAt: Date.now() + LIFETIME_MS,
|
|
10
|
+
filename,
|
|
11
|
+
...user
|
|
12
|
+
};
|
|
13
|
+
const encodedReceipt = Buffer.from(JSON.stringify(data)).toString('base64url');
|
|
14
|
+
return `${encodedReceipt}.${sign(encodedReceipt, req.payload.secret)}`;
|
|
15
|
+
};
|
|
16
|
+
export const verifyClientUploadReceipt = ({ collectionSlug, req, signedReceipt })=>{
|
|
17
|
+
const [encodedReceipt, signature, extra] = signedReceipt.split('.');
|
|
18
|
+
const expected = encodedReceipt ? sign(encodedReceipt, req.payload.secret) : '';
|
|
19
|
+
if (!encodedReceipt || !signature || extra || !equal(signature, expected)) {
|
|
20
|
+
throwInvalidReference();
|
|
21
|
+
}
|
|
22
|
+
let decodedReceipt;
|
|
23
|
+
try {
|
|
24
|
+
decodedReceipt = JSON.parse(Buffer.from(encodedReceipt, 'base64url').toString());
|
|
25
|
+
} catch {
|
|
26
|
+
throwInvalidReference();
|
|
27
|
+
}
|
|
28
|
+
const user = getUser(req);
|
|
29
|
+
if (!isReceipt(decodedReceipt) || decodedReceipt.collectionSlug !== collectionSlug || decodedReceipt.expiresAt <= Date.now() || decodedReceipt.userCollection !== user.userCollection || decodedReceipt.userID !== user.userID) {
|
|
30
|
+
throwInvalidReference();
|
|
31
|
+
}
|
|
32
|
+
return decodedReceipt;
|
|
33
|
+
};
|
|
34
|
+
function throwInvalidReference() {
|
|
35
|
+
throw new APIError('Invalid or expired client upload reference.', 400);
|
|
36
|
+
}
|
|
37
|
+
const getUser = (req)=>({
|
|
38
|
+
userCollection: req.user?.collection ?? null,
|
|
39
|
+
userID: req.user?.id ?? null
|
|
40
|
+
});
|
|
41
|
+
const sign = (encodedReceipt, secret)=>crypto.createHmac('sha256', `${DOMAIN}:${secret}`).update(encodedReceipt).digest('base64url');
|
|
42
|
+
const equal = (actual, expected)=>{
|
|
43
|
+
const actualBuffer = Buffer.from(actual);
|
|
44
|
+
const expectedBuffer = Buffer.from(expected);
|
|
45
|
+
return actualBuffer.length === expectedBuffer.length && crypto.timingSafeEqual(actualBuffer, expectedBuffer);
|
|
46
|
+
};
|
|
47
|
+
const isReceipt = (value)=>{
|
|
48
|
+
if (!value || typeof value !== 'object') {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const data = value;
|
|
52
|
+
return typeof data.collectionSlug === 'string' && Boolean(data.context) && typeof data.context === 'object' && !Array.isArray(data.context) && typeof data.expiresAt === 'number' && Number.isFinite(data.expiresAt) && typeof data.filename === 'string' && (data.userCollection === null || typeof data.userCollection === 'string') && (data.userID === null || typeof data.userID === 'string' || typeof data.userID === 'number' && Number.isFinite(data.userID));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=clientUploadReceipt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/clientUploadReceipt.ts"],"sourcesContent":["import crypto from 'crypto'\n\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { APIError } from '../errors/APIError.js'\n\nexport type ClientUploadReceipt = {\n collectionSlug: string\n context: Record<string, unknown>\n expiresAt: number\n filename: string\n userCollection: null | string\n userID: null | number | string\n}\n\n// `<base64url payload>.<base64url HMAC signature>`\nexport type SignedClientUploadReceipt = `${string}.${string}`\n\nconst LIFETIME_MS = 6 * 60 * 60 * 1000\nconst DOMAIN = 'payload-client-upload-receipt-v1'\n\nexport const createClientUploadReceipt = ({\n collectionSlug,\n context,\n filename,\n req,\n user = getUser(req),\n}: {\n collectionSlug: string\n context: Record<string, unknown>\n filename: string\n req: PayloadRequest\n user?: Pick<ClientUploadReceipt, 'userCollection' | 'userID'>\n}): SignedClientUploadReceipt => {\n const data: ClientUploadReceipt = {\n collectionSlug,\n context,\n expiresAt: Date.now() + LIFETIME_MS,\n filename,\n ...user,\n }\n const encodedReceipt = Buffer.from(JSON.stringify(data)).toString('base64url')\n\n return `${encodedReceipt}.${sign(encodedReceipt, req.payload.secret)}`\n}\n\nexport const verifyClientUploadReceipt = ({\n collectionSlug,\n req,\n signedReceipt,\n}: {\n collectionSlug: string\n req: PayloadRequest\n signedReceipt: string\n}): ClientUploadReceipt => {\n const [encodedReceipt, signature, extra] = signedReceipt.split('.')\n const expected = encodedReceipt ? sign(encodedReceipt, req.payload.secret) : ''\n\n if (!encodedReceipt || !signature || extra || !equal(signature, expected)) {\n throwInvalidReference()\n }\n\n let decodedReceipt: unknown\n try {\n decodedReceipt = JSON.parse(Buffer.from(encodedReceipt, 'base64url').toString())\n } catch {\n throwInvalidReference()\n }\n\n const user = getUser(req)\n if (\n !isReceipt(decodedReceipt) ||\n decodedReceipt.collectionSlug !== collectionSlug ||\n decodedReceipt.expiresAt <= Date.now() ||\n decodedReceipt.userCollection !== user.userCollection ||\n decodedReceipt.userID !== user.userID\n ) {\n throwInvalidReference()\n }\n\n return decodedReceipt\n}\n\nfunction throwInvalidReference(): never {\n throw new APIError('Invalid or expired client upload reference.', 400)\n}\n\nconst getUser = (req: PayloadRequest) => ({\n userCollection: req.user?.collection ?? null,\n userID: req.user?.id ?? null,\n})\n\nconst sign = (encodedReceipt: string, secret: string) =>\n crypto.createHmac('sha256', `${DOMAIN}:${secret}`).update(encodedReceipt).digest('base64url')\n\nconst equal = (actual: string, expected: string) => {\n const actualBuffer = Buffer.from(actual)\n const expectedBuffer = Buffer.from(expected)\n return (\n actualBuffer.length === expectedBuffer.length &&\n crypto.timingSafeEqual(actualBuffer, expectedBuffer)\n )\n}\n\nconst isReceipt = (value: unknown): value is ClientUploadReceipt => {\n if (!value || typeof value !== 'object') {\n return false\n }\n const data = value as Partial<ClientUploadReceipt>\n return (\n typeof data.collectionSlug === 'string' &&\n Boolean(data.context) &&\n typeof data.context === 'object' &&\n !Array.isArray(data.context) &&\n typeof data.expiresAt === 'number' &&\n Number.isFinite(data.expiresAt) &&\n typeof data.filename === 'string' &&\n (data.userCollection === null || typeof data.userCollection === 'string') &&\n (data.userID === null ||\n typeof data.userID === 'string' ||\n (typeof data.userID === 'number' && Number.isFinite(data.userID)))\n )\n}\n"],"names":["crypto","APIError","LIFETIME_MS","DOMAIN","createClientUploadReceipt","collectionSlug","context","filename","req","user","getUser","data","expiresAt","Date","now","encodedReceipt","Buffer","from","JSON","stringify","toString","sign","payload","secret","verifyClientUploadReceipt","signedReceipt","signature","extra","split","expected","equal","throwInvalidReference","decodedReceipt","parse","isReceipt","userCollection","userID","collection","id","createHmac","update","digest","actual","actualBuffer","expectedBuffer","length","timingSafeEqual","value","Boolean","Array","isArray","Number","isFinite"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deleteAssociatedFiles.d.ts","sourceRoot":"","sources":["../../src/uploads/deleteAssociatedFiles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deleteAssociatedFiles.d.ts","sourceRoot":"","sources":["../../src/uploads/deleteAssociatedFiles.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAY,UAAU,EAAE,MAAM,YAAY,CAAA;AAKtD,KAAK,IAAI,GAAG;IACV,gBAAgB,EAAE,yBAAyB,CAAA;IAC3C,MAAM,EAAE,eAAe,CAAA;IACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC5B,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;IACpB,cAAc,EAAE,OAAO,CAAA;IACvB,GAAG,EAAE,cAAc,CAAA;CACpB,CAAA;AAuCD,eAAO,MAAM,qBAAqB,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAkC/D,CAAA"}
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
2
3
|
import { ErrorDeletingFile } from '../errors/index.js';
|
|
3
4
|
import { fileExists } from './fileExists.js';
|
|
5
|
+
const isPathWithin = (parentPath, targetPath)=>{
|
|
6
|
+
const relativePath = path.relative(parentPath, targetPath);
|
|
7
|
+
return relativePath === '' || !path.isAbsolute(relativePath) && relativePath !== '..' && !relativePath.startsWith(`..${path.sep}`);
|
|
8
|
+
};
|
|
9
|
+
const deleteFile = async ({ filename, staticPath })=>{
|
|
10
|
+
if (!filename || !staticPath) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const resolvedStaticPath = path.resolve(staticPath);
|
|
14
|
+
const filePath = path.resolve(resolvedStaticPath, filename);
|
|
15
|
+
if (!isPathWithin(resolvedStaticPath, filePath)) {
|
|
16
|
+
throw new Error('Invalid filename');
|
|
17
|
+
}
|
|
18
|
+
if (await fileExists(filePath)) {
|
|
19
|
+
const [canonicalParentPath, canonicalStaticPath] = await Promise.all([
|
|
20
|
+
fs.realpath(path.dirname(filePath)),
|
|
21
|
+
fs.realpath(resolvedStaticPath)
|
|
22
|
+
]);
|
|
23
|
+
if (!isPathWithin(canonicalStaticPath, canonicalParentPath)) {
|
|
24
|
+
throw new Error('Invalid filename');
|
|
25
|
+
}
|
|
26
|
+
await fs.unlink(filePath);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
4
29
|
export const deleteAssociatedFiles = async ({ collectionConfig, doc, files = [], overrideDelete, req })=>{
|
|
5
30
|
if (!collectionConfig.upload) {
|
|
6
31
|
return;
|
|
7
32
|
}
|
|
8
33
|
if (overrideDelete || files.length > 0) {
|
|
9
34
|
const { staticDir: staticPath } = collectionConfig.upload;
|
|
10
|
-
const fileToDelete = `${staticPath}/${doc.filename}`;
|
|
11
35
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
36
|
+
await deleteFile({
|
|
37
|
+
filename: doc.filename,
|
|
38
|
+
staticPath
|
|
39
|
+
});
|
|
15
40
|
} catch (ignore) {
|
|
16
41
|
throw new ErrorDeletingFile(req.t);
|
|
17
42
|
}
|
|
@@ -21,11 +46,11 @@ export const deleteAssociatedFiles = async ({ collectionConfig, doc, files = [],
|
|
|
21
46
|
// happen that two operations will try to delete the same file.
|
|
22
47
|
// To avoid this it is recommended to use "sync" instead
|
|
23
48
|
for (const size of sizes){
|
|
24
|
-
const sizeToDelete = `${staticPath}/${size.filename}`;
|
|
25
49
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
await deleteFile({
|
|
51
|
+
filename: size.filename,
|
|
52
|
+
staticPath
|
|
53
|
+
});
|
|
29
54
|
} catch (ignore) {
|
|
30
55
|
throw new ErrorDeletingFile(req.t);
|
|
31
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/uploads/deleteAssociatedFiles.ts"],"sourcesContent":["import fs from 'fs/promises'\n\nimport type { SanitizedCollectionConfig } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\nimport type { FileData, FileToSave } from './types.js'\n\nimport { ErrorDeletingFile } from '../errors/index.js'\nimport { fileExists } from './fileExists.js'\n\ntype Args = {\n collectionConfig: SanitizedCollectionConfig\n config: SanitizedConfig\n doc: Record<string, unknown>\n files?: FileToSave[]\n overrideDelete: boolean\n req: PayloadRequest\n}\n\nexport const deleteAssociatedFiles: (args: Args) => Promise<void> = async ({\n collectionConfig,\n doc,\n files = [],\n overrideDelete,\n req,\n}) => {\n if (!collectionConfig.upload) {\n return\n }\n if (overrideDelete || files.length > 0) {\n const { staticDir: staticPath } = collectionConfig.upload\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/deleteAssociatedFiles.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\n\nimport type { SanitizedCollectionConfig } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\nimport type { FileData, FileToSave } from './types.js'\n\nimport { ErrorDeletingFile } from '../errors/index.js'\nimport { fileExists } from './fileExists.js'\n\ntype Args = {\n collectionConfig: SanitizedCollectionConfig\n config: SanitizedConfig\n doc: Record<string, unknown>\n files?: FileToSave[]\n overrideDelete: boolean\n req: PayloadRequest\n}\n\nconst isPathWithin = (parentPath: string, targetPath: string): boolean => {\n const relativePath = path.relative(parentPath, targetPath)\n\n return (\n relativePath === '' ||\n (!path.isAbsolute(relativePath) &&\n relativePath !== '..' &&\n !relativePath.startsWith(`..${path.sep}`))\n )\n}\n\nconst deleteFile = async ({ filename, staticPath }: { filename?: string; staticPath?: string }) => {\n if (!filename || !staticPath) {\n return\n }\n\n const resolvedStaticPath = path.resolve(staticPath)\n const filePath = path.resolve(resolvedStaticPath, filename)\n\n if (!isPathWithin(resolvedStaticPath, filePath)) {\n throw new Error('Invalid filename')\n }\n\n if (await fileExists(filePath)) {\n const [canonicalParentPath, canonicalStaticPath] = await Promise.all([\n fs.realpath(path.dirname(filePath)),\n fs.realpath(resolvedStaticPath),\n ])\n\n if (!isPathWithin(canonicalStaticPath, canonicalParentPath)) {\n throw new Error('Invalid filename')\n }\n\n await fs.unlink(filePath)\n }\n}\n\nexport const deleteAssociatedFiles: (args: Args) => Promise<void> = async ({\n collectionConfig,\n doc,\n files = [],\n overrideDelete,\n req,\n}) => {\n if (!collectionConfig.upload) {\n return\n }\n if (overrideDelete || files.length > 0) {\n const { staticDir: staticPath } = collectionConfig.upload\n\n try {\n await deleteFile({ filename: doc.filename as string | undefined, staticPath })\n } catch (ignore) {\n throw new ErrorDeletingFile(req.t)\n }\n\n if (doc.sizes) {\n const sizes: FileData[] = Object.values(doc.sizes)\n // Since forEach will not wait until unlink is finished it could\n // happen that two operations will try to delete the same file.\n // To avoid this it is recommended to use \"sync\" instead\n\n for (const size of sizes) {\n try {\n await deleteFile({ filename: size.filename, staticPath })\n } catch (ignore) {\n throw new ErrorDeletingFile(req.t)\n }\n }\n }\n }\n}\n"],"names":["fs","path","ErrorDeletingFile","fileExists","isPathWithin","parentPath","targetPath","relativePath","relative","isAbsolute","startsWith","sep","deleteFile","filename","staticPath","resolvedStaticPath","resolve","filePath","Error","canonicalParentPath","canonicalStaticPath","Promise","all","realpath","dirname","unlink","deleteAssociatedFiles","collectionConfig","doc","files","overrideDelete","req","upload","length","staticDir","ignore","t","sizes","Object","values","size"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AAOvB,SAASC,iBAAiB,QAAQ,qBAAoB;AACtD,SAASC,UAAU,QAAQ,kBAAiB;AAW5C,MAAMC,eAAe,CAACC,YAAoBC;IACxC,MAAMC,eAAeN,KAAKO,QAAQ,CAACH,YAAYC;IAE/C,OACEC,iBAAiB,MAChB,CAACN,KAAKQ,UAAU,CAACF,iBAChBA,iBAAiB,QACjB,CAACA,aAAaG,UAAU,CAAC,CAAC,EAAE,EAAET,KAAKU,GAAG,EAAE;AAE9C;AAEA,MAAMC,aAAa,OAAO,EAAEC,QAAQ,EAAEC,UAAU,EAA8C;IAC5F,IAAI,CAACD,YAAY,CAACC,YAAY;QAC5B;IACF;IAEA,MAAMC,qBAAqBd,KAAKe,OAAO,CAACF;IACxC,MAAMG,WAAWhB,KAAKe,OAAO,CAACD,oBAAoBF;IAElD,IAAI,CAACT,aAAaW,oBAAoBE,WAAW;QAC/C,MAAM,IAAIC,MAAM;IAClB;IAEA,IAAI,MAAMf,WAAWc,WAAW;QAC9B,MAAM,CAACE,qBAAqBC,oBAAoB,GAAG,MAAMC,QAAQC,GAAG,CAAC;YACnEtB,GAAGuB,QAAQ,CAACtB,KAAKuB,OAAO,CAACP;YACzBjB,GAAGuB,QAAQ,CAACR;SACb;QAED,IAAI,CAACX,aAAagB,qBAAqBD,sBAAsB;YAC3D,MAAM,IAAID,MAAM;QAClB;QAEA,MAAMlB,GAAGyB,MAAM,CAACR;IAClB;AACF;AAEA,OAAO,MAAMS,wBAAuD,OAAO,EACzEC,gBAAgB,EAChBC,GAAG,EACHC,QAAQ,EAAE,EACVC,cAAc,EACdC,GAAG,EACJ;IACC,IAAI,CAACJ,iBAAiBK,MAAM,EAAE;QAC5B;IACF;IACA,IAAIF,kBAAkBD,MAAMI,MAAM,GAAG,GAAG;QACtC,MAAM,EAAEC,WAAWpB,UAAU,EAAE,GAAGa,iBAAiBK,MAAM;QAEzD,IAAI;YACF,MAAMpB,WAAW;gBAAEC,UAAUe,IAAIf,QAAQ;gBAAwBC;YAAW;QAC9E,EAAE,OAAOqB,QAAQ;YACf,MAAM,IAAIjC,kBAAkB6B,IAAIK,CAAC;QACnC;QAEA,IAAIR,IAAIS,KAAK,EAAE;YACb,MAAMA,QAAoBC,OAAOC,MAAM,CAACX,IAAIS,KAAK;YACjD,gEAAgE;YAChE,+DAA+D;YAC/D,wDAAwD;YAExD,KAAK,MAAMG,QAAQH,MAAO;gBACxB,IAAI;oBACF,MAAMzB,WAAW;wBAAEC,UAAU2B,KAAK3B,QAAQ;wBAAEC;oBAAW;gBACzD,EAAE,OAAOqB,QAAQ;oBACf,MAAM,IAAIjC,kBAAkB6B,IAAIK,CAAC;gBACnC;YACF;QACF;IACF;AACF,EAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { ErrorDeletingFile } from '../errors/index.js';
|
|
6
|
+
import { deleteAssociatedFiles } from './deleteAssociatedFiles.js';
|
|
7
|
+
describe('deleteAssociatedFiles', ()=>{
|
|
8
|
+
let testDir;
|
|
9
|
+
let uploadDir;
|
|
10
|
+
beforeEach(async ()=>{
|
|
11
|
+
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'payload-upload-delete-'));
|
|
12
|
+
uploadDir = path.join(testDir, 'uploads');
|
|
13
|
+
await fs.mkdir(uploadDir);
|
|
14
|
+
});
|
|
15
|
+
afterEach(async ()=>{
|
|
16
|
+
await fs.rm(testDir, {
|
|
17
|
+
force: true,
|
|
18
|
+
recursive: true
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
it('deletes files from the configured upload directory', async ()=>{
|
|
22
|
+
const filename = 'document.txt';
|
|
23
|
+
const filePath = path.join(uploadDir, filename);
|
|
24
|
+
await fs.writeFile(filePath, 'content');
|
|
25
|
+
await deleteAssociatedFiles({
|
|
26
|
+
collectionConfig: {
|
|
27
|
+
upload: {
|
|
28
|
+
staticDir: uploadDir
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
config: {},
|
|
32
|
+
doc: {
|
|
33
|
+
filename
|
|
34
|
+
},
|
|
35
|
+
overrideDelete: true,
|
|
36
|
+
req: {}
|
|
37
|
+
});
|
|
38
|
+
await expect(fs.access(filePath)).rejects.toThrow();
|
|
39
|
+
});
|
|
40
|
+
it('deletes files from nested directories within the configured upload directory', async ()=>{
|
|
41
|
+
const filename = path.join('tenant', 'document.txt');
|
|
42
|
+
const filePath = path.join(uploadDir, filename);
|
|
43
|
+
await fs.mkdir(path.dirname(filePath));
|
|
44
|
+
await fs.writeFile(filePath, 'content');
|
|
45
|
+
await deleteAssociatedFiles({
|
|
46
|
+
collectionConfig: {
|
|
47
|
+
upload: {
|
|
48
|
+
staticDir: uploadDir
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
config: {},
|
|
52
|
+
doc: {
|
|
53
|
+
filename
|
|
54
|
+
},
|
|
55
|
+
overrideDelete: true,
|
|
56
|
+
req: {}
|
|
57
|
+
});
|
|
58
|
+
await expect(fs.access(filePath)).rejects.toThrow();
|
|
59
|
+
});
|
|
60
|
+
it('rejects filenames outside the configured upload directory', async ()=>{
|
|
61
|
+
const filePath = path.join(testDir, 'document.txt');
|
|
62
|
+
await fs.writeFile(filePath, 'content');
|
|
63
|
+
await expect(deleteAssociatedFiles({
|
|
64
|
+
collectionConfig: {
|
|
65
|
+
upload: {
|
|
66
|
+
staticDir: uploadDir
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
config: {},
|
|
70
|
+
doc: {
|
|
71
|
+
filename: path.relative(uploadDir, filePath)
|
|
72
|
+
},
|
|
73
|
+
overrideDelete: true,
|
|
74
|
+
req: {}
|
|
75
|
+
})).rejects.toBeInstanceOf(ErrorDeletingFile);
|
|
76
|
+
await expect(fs.readFile(filePath, 'utf8')).resolves.toBe('content');
|
|
77
|
+
});
|
|
78
|
+
it('rejects generated size filenames outside the configured upload directory', async ()=>{
|
|
79
|
+
const filePath = path.join(testDir, 'thumbnail.txt');
|
|
80
|
+
await fs.writeFile(filePath, 'content');
|
|
81
|
+
await expect(deleteAssociatedFiles({
|
|
82
|
+
collectionConfig: {
|
|
83
|
+
upload: {
|
|
84
|
+
staticDir: uploadDir
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
config: {},
|
|
88
|
+
doc: {
|
|
89
|
+
filename: 'document.txt',
|
|
90
|
+
sizes: {
|
|
91
|
+
thumbnail: {
|
|
92
|
+
filename: path.relative(uploadDir, filePath)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
overrideDelete: true,
|
|
97
|
+
req: {}
|
|
98
|
+
})).rejects.toBeInstanceOf(ErrorDeletingFile);
|
|
99
|
+
await expect(fs.readFile(filePath, 'utf8')).resolves.toBe('content');
|
|
100
|
+
});
|
|
101
|
+
it('rejects files reached through linked directories outside the configured upload directory', async ()=>{
|
|
102
|
+
const externalDir = path.join(testDir, 'documents');
|
|
103
|
+
const filePath = path.join(externalDir, 'document.txt');
|
|
104
|
+
await fs.mkdir(externalDir);
|
|
105
|
+
await fs.writeFile(filePath, 'content');
|
|
106
|
+
await fs.symlink(externalDir, path.join(uploadDir, 'linked'), 'dir');
|
|
107
|
+
await expect(deleteAssociatedFiles({
|
|
108
|
+
collectionConfig: {
|
|
109
|
+
upload: {
|
|
110
|
+
staticDir: uploadDir
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
config: {},
|
|
114
|
+
doc: {
|
|
115
|
+
filename: path.join('linked', 'document.txt')
|
|
116
|
+
},
|
|
117
|
+
overrideDelete: true,
|
|
118
|
+
req: {}
|
|
119
|
+
})).rejects.toBeInstanceOf(ErrorDeletingFile);
|
|
120
|
+
await expect(fs.readFile(filePath, 'utf8')).resolves.toBe('content');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
//# sourceMappingURL=deleteAssociatedFiles.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/uploads/deleteAssociatedFiles.spec.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { afterEach, beforeEach, describe, expect, it } from 'vitest'\n\nimport type { SanitizedCollectionConfig } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { ErrorDeletingFile } from '../errors/index.js'\nimport { deleteAssociatedFiles } from './deleteAssociatedFiles.js'\n\ndescribe('deleteAssociatedFiles', () => {\n let testDir: string\n let uploadDir: string\n\n beforeEach(async () => {\n testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'payload-upload-delete-'))\n uploadDir = path.join(testDir, 'uploads')\n await fs.mkdir(uploadDir)\n })\n\n afterEach(async () => {\n await fs.rm(testDir, { force: true, recursive: true })\n })\n\n it('deletes files from the configured upload directory', async () => {\n const filename = 'document.txt'\n const filePath = path.join(uploadDir, filename)\n await fs.writeFile(filePath, 'content')\n\n await deleteAssociatedFiles({\n collectionConfig: { upload: { staticDir: uploadDir } } as SanitizedCollectionConfig,\n config: {} as SanitizedConfig,\n doc: { filename },\n overrideDelete: true,\n req: {} as PayloadRequest,\n })\n\n await expect(fs.access(filePath)).rejects.toThrow()\n })\n\n it('deletes files from nested directories within the configured upload directory', async () => {\n const filename = path.join('tenant', 'document.txt')\n const filePath = path.join(uploadDir, filename)\n await fs.mkdir(path.dirname(filePath))\n await fs.writeFile(filePath, 'content')\n\n await deleteAssociatedFiles({\n collectionConfig: { upload: { staticDir: uploadDir } } as SanitizedCollectionConfig,\n config: {} as SanitizedConfig,\n doc: { filename },\n overrideDelete: true,\n req: {} as PayloadRequest,\n })\n\n await expect(fs.access(filePath)).rejects.toThrow()\n })\n\n it('rejects filenames outside the configured upload directory', async () => {\n const filePath = path.join(testDir, 'document.txt')\n await fs.writeFile(filePath, 'content')\n\n await expect(\n deleteAssociatedFiles({\n collectionConfig: { upload: { staticDir: uploadDir } } as SanitizedCollectionConfig,\n config: {} as SanitizedConfig,\n doc: { filename: path.relative(uploadDir, filePath) },\n overrideDelete: true,\n req: {} as PayloadRequest,\n }),\n ).rejects.toBeInstanceOf(ErrorDeletingFile)\n\n await expect(fs.readFile(filePath, 'utf8')).resolves.toBe('content')\n })\n\n it('rejects generated size filenames outside the configured upload directory', async () => {\n const filePath = path.join(testDir, 'thumbnail.txt')\n await fs.writeFile(filePath, 'content')\n\n await expect(\n deleteAssociatedFiles({\n collectionConfig: { upload: { staticDir: uploadDir } } as SanitizedCollectionConfig,\n config: {} as SanitizedConfig,\n doc: {\n filename: 'document.txt',\n sizes: { thumbnail: { filename: path.relative(uploadDir, filePath) } },\n },\n overrideDelete: true,\n req: {} as PayloadRequest,\n }),\n ).rejects.toBeInstanceOf(ErrorDeletingFile)\n\n await expect(fs.readFile(filePath, 'utf8')).resolves.toBe('content')\n })\n\n it('rejects files reached through linked directories outside the configured upload directory', async () => {\n const externalDir = path.join(testDir, 'documents')\n const filePath = path.join(externalDir, 'document.txt')\n await fs.mkdir(externalDir)\n await fs.writeFile(filePath, 'content')\n await fs.symlink(externalDir, path.join(uploadDir, 'linked'), 'dir')\n\n await expect(\n deleteAssociatedFiles({\n collectionConfig: { upload: { staticDir: uploadDir } } as SanitizedCollectionConfig,\n config: {} as SanitizedConfig,\n doc: { filename: path.join('linked', 'document.txt') },\n overrideDelete: true,\n req: {} as PayloadRequest,\n }),\n ).rejects.toBeInstanceOf(ErrorDeletingFile)\n\n await expect(fs.readFile(filePath, 'utf8')).resolves.toBe('content')\n })\n})\n"],"names":["fs","os","path","afterEach","beforeEach","describe","expect","it","ErrorDeletingFile","deleteAssociatedFiles","testDir","uploadDir","mkdtemp","join","tmpdir","mkdir","rm","force","recursive","filename","filePath","writeFile","collectionConfig","upload","staticDir","config","doc","overrideDelete","req","access","rejects","toThrow","dirname","relative","toBeInstanceOf","readFile","resolves","toBe","sizes","thumbnail","externalDir","symlink"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectSvgFromXml.d.ts","sourceRoot":"","sources":["../../src/uploads/detectSvgFromXml.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"detectSvgFromXml.d.ts","sourceRoot":"","sources":["../../src/uploads/detectSvgFromXml.ts"],"names":[],"mappings":"AAEA,uEAAuE;AACvE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD"}
|