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.
Files changed (468) hide show
  1. package/dist/admin/forms/Field.d.ts +2 -0
  2. package/dist/admin/forms/Field.d.ts.map +1 -1
  3. package/dist/admin/forms/Field.js.map +1 -1
  4. package/dist/admin/forms/Form.d.ts +2 -0
  5. package/dist/admin/forms/Form.d.ts.map +1 -1
  6. package/dist/admin/forms/Form.js.map +1 -1
  7. package/dist/admin/functions/index.d.ts +3 -1
  8. package/dist/admin/functions/index.d.ts.map +1 -1
  9. package/dist/admin/functions/index.js.map +1 -1
  10. package/dist/admin/views/dashboard.d.ts +1 -1
  11. package/dist/admin/views/dashboard.d.ts.map +1 -1
  12. package/dist/admin/views/dashboard.js.map +1 -1
  13. package/dist/auth/applyUserReadAccess.d.ts +15 -0
  14. package/dist/auth/applyUserReadAccess.d.ts.map +1 -0
  15. package/dist/auth/applyUserReadAccess.js +41 -0
  16. package/dist/auth/applyUserReadAccess.js.map +1 -0
  17. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts +4 -0
  18. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts.map +1 -0
  19. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js +20 -0
  20. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js.map +1 -0
  21. package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts +4 -0
  22. package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts.map +1 -0
  23. package/dist/auth/baseFields/apiKey/encryptAPIKey.js +16 -0
  24. package/dist/auth/baseFields/apiKey/encryptAPIKey.js.map +1 -0
  25. package/dist/auth/baseFields/apiKey/index.d.ts +6 -0
  26. package/dist/auth/baseFields/apiKey/index.d.ts.map +1 -0
  27. package/dist/auth/baseFields/apiKey/index.js +112 -0
  28. package/dist/auth/baseFields/apiKey/index.js.map +1 -0
  29. package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts +4 -0
  30. package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts.map +1 -0
  31. package/dist/auth/baseFields/apiKey/omitAPIKey.js +8 -0
  32. package/dist/auth/baseFields/apiKey/omitAPIKey.js.map +1 -0
  33. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts +4 -0
  34. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts.map +1 -0
  35. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js +5 -0
  36. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js.map +1 -0
  37. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts +4 -0
  38. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts.map +1 -0
  39. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js +5 -0
  40. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js.map +1 -0
  41. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts +4 -0
  42. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts.map +1 -0
  43. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js +17 -0
  44. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js.map +1 -0
  45. package/dist/auth/baseFields/auth.d.ts +1 -0
  46. package/dist/auth/baseFields/auth.d.ts.map +1 -1
  47. package/dist/auth/baseFields/auth.js +9 -0
  48. package/dist/auth/baseFields/auth.js.map +1 -1
  49. package/dist/auth/defaultUnlockAccess.d.ts +3 -0
  50. package/dist/auth/defaultUnlockAccess.d.ts.map +1 -0
  51. package/dist/auth/defaultUnlockAccess.js +8 -0
  52. package/dist/auth/defaultUnlockAccess.js.map +1 -0
  53. package/dist/auth/endpoints/index.d.ts +1 -0
  54. package/dist/auth/endpoints/index.d.ts.map +1 -1
  55. package/dist/auth/endpoints/index.js +8 -0
  56. package/dist/auth/endpoints/index.js.map +1 -1
  57. package/dist/auth/endpoints/revealAPIKey.d.ts +7 -0
  58. package/dist/auth/endpoints/revealAPIKey.d.ts.map +1 -0
  59. package/dist/auth/endpoints/revealAPIKey.js +120 -0
  60. package/dist/auth/endpoints/revealAPIKey.js.map +1 -0
  61. package/dist/auth/executeAccess.d.ts +3 -1
  62. package/dist/auth/executeAccess.d.ts.map +1 -1
  63. package/dist/auth/executeAccess.js +2 -1
  64. package/dist/auth/executeAccess.js.map +1 -1
  65. package/dist/auth/getAuthFields.d.ts +1 -1
  66. package/dist/auth/getAuthFields.d.ts.map +1 -1
  67. package/dist/auth/getAuthFields.js +7 -4
  68. package/dist/auth/getAuthFields.js.map +1 -1
  69. package/dist/auth/getFieldsToSign.d.ts.map +1 -1
  70. package/dist/auth/getFieldsToSign.js +17 -10
  71. package/dist/auth/getFieldsToSign.js.map +1 -1
  72. package/dist/auth/getFieldsToSign.spec.js +114 -0
  73. package/dist/auth/getFieldsToSign.spec.js.map +1 -0
  74. package/dist/auth/jwt.d.ts.map +1 -1
  75. package/dist/auth/jwt.js +2 -0
  76. package/dist/auth/jwt.js.map +1 -1
  77. package/dist/auth/jwt.spec.js +24 -0
  78. package/dist/auth/jwt.spec.js.map +1 -0
  79. package/dist/auth/jwtAuth.d.ts +9 -0
  80. package/dist/auth/jwtAuth.d.ts.map +1 -0
  81. package/dist/auth/jwtAuth.js +8 -0
  82. package/dist/auth/jwtAuth.js.map +1 -0
  83. package/dist/auth/operations/auth.d.ts.map +1 -1
  84. package/dist/auth/operations/auth.js +2 -1
  85. package/dist/auth/operations/auth.js.map +1 -1
  86. package/dist/auth/operations/forgotPassword.d.ts.map +1 -1
  87. package/dist/auth/operations/forgotPassword.js +95 -7
  88. package/dist/auth/operations/forgotPassword.js.map +1 -1
  89. package/dist/auth/operations/local/auth.js +3 -1
  90. package/dist/auth/operations/local/auth.js.map +1 -1
  91. package/dist/auth/operations/login.d.ts.map +1 -1
  92. package/dist/auth/operations/login.js +48 -26
  93. package/dist/auth/operations/login.js.map +1 -1
  94. package/dist/auth/operations/refresh.d.ts.map +1 -1
  95. package/dist/auth/operations/refresh.js +32 -9
  96. package/dist/auth/operations/refresh.js.map +1 -1
  97. package/dist/auth/operations/registerFirstUser.d.ts.map +1 -1
  98. package/dist/auth/operations/registerFirstUser.js +3 -4
  99. package/dist/auth/operations/registerFirstUser.js.map +1 -1
  100. package/dist/auth/operations/resetPassword.d.ts.map +1 -1
  101. package/dist/auth/operations/resetPassword.js +31 -22
  102. package/dist/auth/operations/resetPassword.js.map +1 -1
  103. package/dist/auth/operations/unlock.d.ts.map +1 -1
  104. package/dist/auth/operations/unlock.js +1 -0
  105. package/dist/auth/operations/unlock.js.map +1 -1
  106. package/dist/auth/strategies/apiKey.d.ts.map +1 -1
  107. package/dist/auth/strategies/apiKey.js +15 -1
  108. package/dist/auth/strategies/apiKey.js.map +1 -1
  109. package/dist/auth/strategies/jwt.d.ts.map +1 -1
  110. package/dist/auth/strategies/jwt.js +61 -10
  111. package/dist/auth/strategies/jwt.js.map +1 -1
  112. package/dist/auth/strategies/jwt.spec.js +281 -0
  113. package/dist/auth/strategies/jwt.spec.js.map +1 -0
  114. package/dist/auth/strategies/local/authenticate.d.ts +5 -1
  115. package/dist/auth/strategies/local/authenticate.d.ts.map +1 -1
  116. package/dist/auth/strategies/local/authenticate.js +8 -3
  117. package/dist/auth/strategies/local/authenticate.js.map +1 -1
  118. package/dist/auth/strategies/local/authenticate.spec.js +37 -10
  119. package/dist/auth/strategies/local/authenticate.spec.js.map +1 -1
  120. package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts +9 -1
  121. package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts.map +1 -1
  122. package/dist/auth/strategies/local/generatePasswordSaltHash.js +61 -34
  123. package/dist/auth/strategies/local/generatePasswordSaltHash.js.map +1 -1
  124. package/dist/auth/types.d.ts +16 -1
  125. package/dist/auth/types.d.ts.map +1 -1
  126. package/dist/auth/types.js.map +1 -1
  127. package/dist/collections/config/client.d.ts +1 -0
  128. package/dist/collections/config/client.d.ts.map +1 -1
  129. package/dist/collections/config/client.js +3 -0
  130. package/dist/collections/config/client.js.map +1 -1
  131. package/dist/collections/config/defaults.d.ts.map +1 -1
  132. package/dist/collections/config/defaults.js +4 -2
  133. package/dist/collections/config/defaults.js.map +1 -1
  134. package/dist/collections/config/sanitize.d.ts.map +1 -1
  135. package/dist/collections/config/sanitize.js +17 -6
  136. package/dist/collections/config/sanitize.js.map +1 -1
  137. package/dist/collections/endpoints/index.d.ts +1 -0
  138. package/dist/collections/endpoints/index.d.ts.map +1 -1
  139. package/dist/collections/endpoints/index.js +6 -5
  140. package/dist/collections/endpoints/index.js.map +1 -1
  141. package/dist/collections/operations/create.d.ts.map +1 -1
  142. package/dist/collections/operations/create.js +105 -8
  143. package/dist/collections/operations/create.js.map +1 -1
  144. package/dist/collections/operations/deleteByID.js +13 -13
  145. package/dist/collections/operations/deleteByID.js.map +1 -1
  146. package/dist/collections/operations/findDistinct.d.ts.map +1 -1
  147. package/dist/collections/operations/findDistinct.js +96 -18
  148. package/dist/collections/operations/findDistinct.js.map +1 -1
  149. package/dist/collections/operations/restoreVersion.d.ts.map +1 -1
  150. package/dist/collections/operations/restoreVersion.js +47 -13
  151. package/dist/collections/operations/restoreVersion.js.map +1 -1
  152. package/dist/collections/operations/update.d.ts.map +1 -1
  153. package/dist/collections/operations/update.js +123 -23
  154. package/dist/collections/operations/update.js.map +1 -1
  155. package/dist/collections/operations/updateByID.d.ts.map +1 -1
  156. package/dist/collections/operations/updateByID.js +62 -2
  157. package/dist/collections/operations/updateByID.js.map +1 -1
  158. package/dist/collections/operations/utilities/update.d.ts.map +1 -1
  159. package/dist/collections/operations/utilities/update.js +71 -15
  160. package/dist/collections/operations/utilities/update.js.map +1 -1
  161. package/dist/config/orderable/index.d.ts.map +1 -1
  162. package/dist/config/orderable/index.js +67 -14
  163. package/dist/config/orderable/index.js.map +1 -1
  164. package/dist/config/sanitize.d.ts.map +1 -1
  165. package/dist/config/sanitize.js +1 -1
  166. package/dist/config/sanitize.js.map +1 -1
  167. package/dist/config/types.d.ts +13 -1
  168. package/dist/config/types.d.ts.map +1 -1
  169. package/dist/config/types.js.map +1 -1
  170. package/dist/database/getLocalizedPaths.d.ts +2 -1
  171. package/dist/database/getLocalizedPaths.d.ts.map +1 -1
  172. package/dist/database/getLocalizedPaths.js +12 -3
  173. package/dist/database/getLocalizedPaths.js.map +1 -1
  174. package/dist/database/isNestedRelationshipQuery.d.ts +9 -0
  175. package/dist/database/isNestedRelationshipQuery.d.ts.map +1 -0
  176. package/dist/database/isNestedRelationshipQuery.js +14 -0
  177. package/dist/database/isNestedRelationshipQuery.js.map +1 -0
  178. package/dist/database/prefixWherePaths.d.ts +6 -0
  179. package/dist/database/prefixWherePaths.d.ts.map +1 -0
  180. package/dist/database/prefixWherePaths.js +19 -0
  181. package/dist/database/prefixWherePaths.js.map +1 -0
  182. package/dist/database/prefixWherePaths.spec.js +46 -0
  183. package/dist/database/prefixWherePaths.spec.js.map +1 -0
  184. package/dist/database/queryValidation/validateQueryPaths.d.ts +2 -1
  185. package/dist/database/queryValidation/validateQueryPaths.d.ts.map +1 -1
  186. package/dist/database/queryValidation/validateQueryPaths.js +4 -1
  187. package/dist/database/queryValidation/validateQueryPaths.js.map +1 -1
  188. package/dist/database/queryValidation/validateSearchParams.d.ts +2 -1
  189. package/dist/database/queryValidation/validateSearchParams.d.ts.map +1 -1
  190. package/dist/database/queryValidation/validateSearchParams.js +82 -4
  191. package/dist/database/queryValidation/validateSearchParams.js.map +1 -1
  192. package/dist/database/types.d.ts +5 -0
  193. package/dist/database/types.d.ts.map +1 -1
  194. package/dist/database/types.js.map +1 -1
  195. package/dist/email/normalizeSendEmailOptions.js.map +1 -1
  196. package/dist/email/types.d.ts +4 -1
  197. package/dist/email/types.d.ts.map +1 -1
  198. package/dist/email/types.js.map +1 -1
  199. package/dist/exports/internal.d.ts +7 -0
  200. package/dist/exports/internal.d.ts.map +1 -1
  201. package/dist/exports/internal.js +7 -1
  202. package/dist/exports/internal.js.map +1 -1
  203. package/dist/exports/shared.d.ts +3 -0
  204. package/dist/exports/shared.d.ts.map +1 -1
  205. package/dist/exports/shared.js +3 -0
  206. package/dist/exports/shared.js.map +1 -1
  207. package/dist/fields/config/types.d.ts +7 -5
  208. package/dist/fields/config/types.d.ts.map +1 -1
  209. package/dist/fields/config/types.js.map +1 -1
  210. package/dist/fields/hooks/beforeChange/index.d.ts +7 -1
  211. package/dist/fields/hooks/beforeChange/index.d.ts.map +1 -1
  212. package/dist/fields/hooks/beforeChange/index.js +4 -2
  213. package/dist/fields/hooks/beforeChange/index.js.map +1 -1
  214. package/dist/fields/hooks/beforeChange/index.spec.js +206 -0
  215. package/dist/fields/hooks/beforeChange/index.spec.js.map +1 -0
  216. package/dist/fields/hooks/beforeChange/promise.d.ts +2 -1
  217. package/dist/fields/hooks/beforeChange/promise.d.ts.map +1 -1
  218. package/dist/fields/hooks/beforeChange/promise.js +51 -9
  219. package/dist/fields/hooks/beforeChange/promise.js.map +1 -1
  220. package/dist/fields/hooks/beforeChange/traverseFields.d.ts +2 -1
  221. package/dist/fields/hooks/beforeChange/traverseFields.d.ts.map +1 -1
  222. package/dist/fields/hooks/beforeChange/traverseFields.js +3 -2
  223. package/dist/fields/hooks/beforeChange/traverseFields.js.map +1 -1
  224. package/dist/fields/hooks/beforeDuplicate/promise.d.ts.map +1 -1
  225. package/dist/fields/hooks/beforeDuplicate/promise.js +4 -0
  226. package/dist/fields/hooks/beforeDuplicate/promise.js.map +1 -1
  227. package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts +2 -1
  228. package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts.map +1 -1
  229. package/dist/fields/hooks/beforeValidate/getFallbackValue.js +2 -2
  230. package/dist/fields/hooks/beforeValidate/getFallbackValue.js.map +1 -1
  231. package/dist/fields/hooks/beforeValidate/index.d.ts +6 -1
  232. package/dist/fields/hooks/beforeValidate/index.d.ts.map +1 -1
  233. package/dist/fields/hooks/beforeValidate/index.js +3 -1
  234. package/dist/fields/hooks/beforeValidate/index.js.map +1 -1
  235. package/dist/fields/hooks/beforeValidate/index.spec.js +90 -0
  236. package/dist/fields/hooks/beforeValidate/index.spec.js.map +1 -0
  237. package/dist/fields/hooks/beforeValidate/promise.d.ts +6 -1
  238. package/dist/fields/hooks/beforeValidate/promise.d.ts.map +1 -1
  239. package/dist/fields/hooks/beforeValidate/promise.js +23 -8
  240. package/dist/fields/hooks/beforeValidate/promise.js.map +1 -1
  241. package/dist/fields/hooks/beforeValidate/traverseFields.d.ts +6 -1
  242. package/dist/fields/hooks/beforeValidate/traverseFields.d.ts.map +1 -1
  243. package/dist/fields/hooks/beforeValidate/traverseFields.js +3 -1
  244. package/dist/fields/hooks/beforeValidate/traverseFields.js.map +1 -1
  245. package/dist/fields/validations.d.ts.map +1 -1
  246. package/dist/fields/validations.js +3 -1
  247. package/dist/fields/validations.js.map +1 -1
  248. package/dist/globals/operations/restoreVersion.d.ts.map +1 -1
  249. package/dist/globals/operations/restoreVersion.js +45 -11
  250. package/dist/globals/operations/restoreVersion.js.map +1 -1
  251. package/dist/globals/operations/update.d.ts.map +1 -1
  252. package/dist/globals/operations/update.js +95 -16
  253. package/dist/globals/operations/update.js.map +1 -1
  254. package/dist/index.bundled.d.ts +146 -48
  255. package/dist/index.d.ts +4 -1
  256. package/dist/index.d.ts.map +1 -1
  257. package/dist/index.js +3 -2
  258. package/dist/index.js.map +1 -1
  259. package/dist/preferences/config.d.ts.map +1 -1
  260. package/dist/preferences/config.js +2 -1
  261. package/dist/preferences/config.js.map +1 -1
  262. package/dist/query-presets/access.d.ts.map +1 -1
  263. package/dist/query-presets/access.js +2 -1
  264. package/dist/query-presets/access.js.map +1 -1
  265. package/dist/queues/config/collection.d.ts.map +1 -1
  266. package/dist/queues/config/collection.js +6 -0
  267. package/dist/queues/config/collection.js.map +1 -1
  268. package/dist/queues/config/types/index.d.ts +4 -1
  269. package/dist/queues/config/types/index.d.ts.map +1 -1
  270. package/dist/queues/config/types/index.js.map +1 -1
  271. package/dist/queues/endpoints/handleSchedules.d.ts.map +1 -1
  272. package/dist/queues/endpoints/handleSchedules.js +2 -1
  273. package/dist/queues/endpoints/handleSchedules.js.map +1 -1
  274. package/dist/queues/endpoints/run.d.ts.map +1 -1
  275. package/dist/queues/endpoints/run.js +2 -1
  276. package/dist/queues/endpoints/run.js.map +1 -1
  277. package/dist/uploads/assertClientUploadAccess.d.ts +7 -0
  278. package/dist/uploads/assertClientUploadAccess.d.ts.map +1 -0
  279. package/dist/uploads/assertClientUploadAccess.js +27 -0
  280. package/dist/uploads/assertClientUploadAccess.js.map +1 -0
  281. package/dist/uploads/assertClientUploadAllowed.d.ts +11 -0
  282. package/dist/uploads/assertClientUploadAllowed.d.ts.map +1 -0
  283. package/dist/uploads/assertClientUploadAllowed.js +46 -0
  284. package/dist/uploads/assertClientUploadAllowed.js.map +1 -0
  285. package/dist/uploads/assertClientUploadAllowed.spec.js +91 -0
  286. package/dist/uploads/assertClientUploadAllowed.spec.js.map +1 -0
  287. package/dist/uploads/checkFileRestrictions.d.ts +4 -1
  288. package/dist/uploads/checkFileRestrictions.d.ts.map +1 -1
  289. package/dist/uploads/checkFileRestrictions.js +66 -26
  290. package/dist/uploads/checkFileRestrictions.js.map +1 -1
  291. package/dist/uploads/clientUploadReceipt.d.ts +23 -0
  292. package/dist/uploads/clientUploadReceipt.d.ts.map +1 -0
  293. package/dist/uploads/clientUploadReceipt.js +55 -0
  294. package/dist/uploads/clientUploadReceipt.js.map +1 -0
  295. package/dist/uploads/deleteAssociatedFiles.d.ts.map +1 -1
  296. package/dist/uploads/deleteAssociatedFiles.js +33 -8
  297. package/dist/uploads/deleteAssociatedFiles.js.map +1 -1
  298. package/dist/uploads/deleteAssociatedFiles.spec.js +124 -0
  299. package/dist/uploads/deleteAssociatedFiles.spec.js.map +1 -0
  300. package/dist/uploads/detectSvgFromXml.d.ts +1 -3
  301. package/dist/uploads/detectSvgFromXml.d.ts.map +1 -1
  302. package/dist/uploads/detectSvgFromXml.js +3 -40
  303. package/dist/uploads/detectSvgFromXml.js.map +1 -1
  304. package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
  305. package/dist/uploads/endpoints/getFile.js +5 -3
  306. package/dist/uploads/endpoints/getFile.js.map +1 -1
  307. package/dist/uploads/fetchAPI-multipart/handlers.d.ts +2 -2
  308. package/dist/uploads/fetchAPI-multipart/handlers.d.ts.map +1 -1
  309. package/dist/uploads/fetchAPI-multipart/handlers.js +18 -5
  310. package/dist/uploads/fetchAPI-multipart/handlers.js.map +1 -1
  311. package/dist/uploads/fetchAPI-multipart/index.d.ts.map +1 -1
  312. package/dist/uploads/fetchAPI-multipart/index.js +24 -2
  313. package/dist/uploads/fetchAPI-multipart/index.js.map +1 -1
  314. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts +0 -6
  315. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts.map +1 -1
  316. package/dist/uploads/fetchAPI-multipart/processMultipart.js +146 -126
  317. package/dist/uploads/fetchAPI-multipart/processMultipart.js.map +1 -1
  318. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js +663 -0
  319. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js.map +1 -0
  320. package/dist/uploads/generateFileData.d.ts +3 -1
  321. package/dist/uploads/generateFileData.d.ts.map +1 -1
  322. package/dist/uploads/generateFileData.js +29 -16
  323. package/dist/uploads/generateFileData.js.map +1 -1
  324. package/dist/uploads/getBaseFields.d.ts.map +1 -1
  325. package/dist/uploads/getBaseFields.js +3 -1
  326. package/dist/uploads/getBaseFields.js.map +1 -1
  327. package/dist/uploads/getExternalFile.d.ts +1 -1
  328. package/dist/uploads/getExternalFile.d.ts.map +1 -1
  329. package/dist/uploads/getExternalFile.js +60 -15
  330. package/dist/uploads/getExternalFile.js.map +1 -1
  331. package/dist/uploads/getFileFromClientUpload.d.ts +2 -2
  332. package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -1
  333. package/dist/uploads/getFileFromClientUpload.js +4 -4
  334. package/dist/uploads/getFileFromClientUpload.js.map +1 -1
  335. package/dist/uploads/getFileFromClientUpload.spec.js +62 -0
  336. package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -1
  337. package/dist/uploads/getFileTypeFallback.d.ts.map +1 -1
  338. package/dist/uploads/getFileTypeFallback.js +3 -1
  339. package/dist/uploads/getFileTypeFallback.js.map +1 -1
  340. package/dist/uploads/getFileTypeIdentity.d.ts +19 -0
  341. package/dist/uploads/getFileTypeIdentity.d.ts.map +1 -0
  342. package/dist/uploads/getFileTypeIdentity.js +34 -0
  343. package/dist/uploads/getFileTypeIdentity.js.map +1 -0
  344. package/dist/uploads/getFileTypeIdentity.spec.js +43 -0
  345. package/dist/uploads/getFileTypeIdentity.spec.js.map +1 -0
  346. package/dist/uploads/isProcessableImage.d.ts +2 -0
  347. package/dist/uploads/isProcessableImage.d.ts.map +1 -0
  348. package/dist/uploads/isProcessableImage.js +7 -0
  349. package/dist/uploads/isProcessableImage.js.map +1 -0
  350. package/dist/uploads/safeFetch.d.ts.map +1 -1
  351. package/dist/uploads/safeFetch.js +3 -2
  352. package/dist/uploads/safeFetch.js.map +1 -1
  353. package/dist/uploads/sanitizeUploadData.d.ts +26 -0
  354. package/dist/uploads/sanitizeUploadData.d.ts.map +1 -0
  355. package/dist/uploads/sanitizeUploadData.js +159 -0
  356. package/dist/uploads/sanitizeUploadData.js.map +1 -0
  357. package/dist/uploads/sanitizeUploadData.spec.js +255 -0
  358. package/dist/uploads/sanitizeUploadData.spec.js.map +1 -0
  359. package/dist/uploads/types.d.ts +11 -3
  360. package/dist/uploads/types.d.ts.map +1 -1
  361. package/dist/uploads/types.js.map +1 -1
  362. package/dist/uploads/unlinkClientUploadTempFile.d.ts +20 -0
  363. package/dist/uploads/unlinkClientUploadTempFile.d.ts.map +1 -0
  364. package/dist/uploads/unlinkClientUploadTempFile.js +30 -0
  365. package/dist/uploads/unlinkClientUploadTempFile.js.map +1 -0
  366. package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
  367. package/dist/uploads/unlinkTempFiles.js +7 -9
  368. package/dist/uploads/unlinkTempFiles.js.map +1 -1
  369. package/dist/uploads/unlinkTempFiles.spec.js +61 -1
  370. package/dist/uploads/unlinkTempFiles.spec.js.map +1 -1
  371. package/dist/uploads/uploadContentSecurityPolicy.d.ts +2 -0
  372. package/dist/uploads/uploadContentSecurityPolicy.d.ts.map +1 -0
  373. package/dist/uploads/uploadContentSecurityPolicy.js +3 -0
  374. package/dist/uploads/uploadContentSecurityPolicy.js.map +1 -0
  375. package/dist/uploads/validateISOBaseMediaFile.d.ts +4 -0
  376. package/dist/uploads/validateISOBaseMediaFile.d.ts.map +1 -0
  377. package/dist/uploads/validateISOBaseMediaFile.js +91 -0
  378. package/dist/uploads/validateISOBaseMediaFile.js.map +1 -0
  379. package/dist/uploads/validateISOBaseMediaFile.spec.js +130 -0
  380. package/dist/uploads/validateISOBaseMediaFile.spec.js.map +1 -0
  381. package/dist/uploads/validateSvg.d.ts +10 -4
  382. package/dist/uploads/validateSvg.d.ts.map +1 -1
  383. package/dist/uploads/validateSvg.js +280 -40
  384. package/dist/uploads/validateSvg.js.map +1 -1
  385. package/dist/uploads/validateSvg.spec.js +123 -0
  386. package/dist/uploads/validateSvg.spec.js.map +1 -0
  387. package/dist/uploads/validateUploadFilename.d.ts +3 -0
  388. package/dist/uploads/validateUploadFilename.d.ts.map +1 -0
  389. package/dist/uploads/validateUploadFilename.js +13 -0
  390. package/dist/uploads/validateUploadFilename.js.map +1 -0
  391. package/dist/uploads/validateUploadFilename.spec.js +24 -0
  392. package/dist/uploads/validateUploadFilename.spec.js.map +1 -0
  393. package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
  394. package/dist/utilities/addDataAndFileToRequest.js +28 -1
  395. package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
  396. package/dist/utilities/addDataAndFileToRequest.spec.js +158 -0
  397. package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
  398. package/dist/utilities/createPayloadRequest.d.ts.map +1 -1
  399. package/dist/utilities/createPayloadRequest.js +2 -1
  400. package/dist/utilities/createPayloadRequest.js.map +1 -1
  401. package/dist/utilities/deepCopyObject.d.ts.map +1 -1
  402. package/dist/utilities/deepCopyObject.js +110 -19
  403. package/dist/utilities/deepCopyObject.js.map +1 -1
  404. package/dist/utilities/deepCopyObject.spec.js +216 -0
  405. package/dist/utilities/deepCopyObject.spec.js.map +1 -0
  406. package/dist/utilities/fieldPath.d.ts +13 -0
  407. package/dist/utilities/fieldPath.d.ts.map +1 -0
  408. package/dist/utilities/fieldPath.js +51 -0
  409. package/dist/utilities/fieldPath.js.map +1 -0
  410. package/dist/utilities/fieldPath.spec.js +155 -0
  411. package/dist/utilities/fieldPath.spec.js.map +1 -0
  412. package/dist/utilities/getEntityPermissions/getEntityPermissions.d.ts.map +1 -1
  413. package/dist/utilities/getEntityPermissions/getEntityPermissions.js +2 -0
  414. package/dist/utilities/getEntityPermissions/getEntityPermissions.js.map +1 -1
  415. package/dist/utilities/getObjectDotNotation.d.ts.map +1 -1
  416. package/dist/utilities/getObjectDotNotation.js +7 -1
  417. package/dist/utilities/getObjectDotNotation.js.map +1 -1
  418. package/dist/utilities/getObjectDotNotation.spec.js +33 -0
  419. package/dist/utilities/getObjectDotNotation.spec.js.map +1 -0
  420. package/dist/utilities/getSafeRedirect.spec.js +14 -0
  421. package/dist/utilities/getSafeRedirect.spec.js.map +1 -1
  422. package/dist/utilities/getTopLevelFieldNames.d.ts +2 -0
  423. package/dist/utilities/getTopLevelFieldNames.d.ts.map +1 -0
  424. package/dist/utilities/getTopLevelFieldNames.js +3 -0
  425. package/dist/utilities/getTopLevelFieldNames.js.map +1 -0
  426. package/dist/utilities/getTopLevelFieldNames.spec.js +18 -0
  427. package/dist/utilities/getTopLevelFieldNames.spec.js.map +1 -0
  428. package/dist/utilities/handleEndpoints.js +1 -1
  429. package/dist/utilities/handleEndpoints.js.map +1 -1
  430. package/dist/utilities/parseParams/index.d.ts.map +1 -1
  431. package/dist/utilities/parseParams/index.js +3 -1
  432. package/dist/utilities/parseParams/index.js.map +1 -1
  433. package/dist/utilities/parseParams/index.spec.js +8 -0
  434. package/dist/utilities/parseParams/index.spec.js.map +1 -1
  435. package/dist/utilities/traverseFields.d.ts.map +1 -1
  436. package/dist/utilities/traverseFields.js +5 -14
  437. package/dist/utilities/traverseFields.js.map +1 -1
  438. package/dist/utilities/unflatten.d.ts.map +1 -1
  439. package/dist/utilities/unflatten.js +21 -9
  440. package/dist/utilities/unflatten.js.map +1 -1
  441. package/dist/utilities/unflatten.spec.js +38 -0
  442. package/dist/utilities/unflatten.spec.js.map +1 -0
  443. package/dist/utilities/wrapInternalEndpoints.d.ts.map +1 -1
  444. package/dist/utilities/wrapInternalEndpoints.js +10 -3
  445. package/dist/utilities/wrapInternalEndpoints.js.map +1 -1
  446. package/dist/utilities/wrapInternalEndpoints.spec.js +124 -0
  447. package/dist/utilities/wrapInternalEndpoints.spec.js.map +1 -0
  448. package/dist/versions/allLocalesPublicationStatus.d.ts +38 -0
  449. package/dist/versions/allLocalesPublicationStatus.d.ts.map +1 -0
  450. package/dist/versions/allLocalesPublicationStatus.js +57 -0
  451. package/dist/versions/allLocalesPublicationStatus.js.map +1 -0
  452. package/dist/versions/getLatestGlobalVersion.d.ts.map +1 -1
  453. package/dist/versions/getLatestGlobalVersion.js +10 -2
  454. package/dist/versions/getLatestGlobalVersion.js.map +1 -1
  455. package/dist/versions/getRestoredStatusesToAuthorize.d.ts +9 -0
  456. package/dist/versions/getRestoredStatusesToAuthorize.d.ts.map +1 -0
  457. package/dist/versions/getRestoredStatusesToAuthorize.js +22 -0
  458. package/dist/versions/getRestoredStatusesToAuthorize.js.map +1 -0
  459. package/dist/versions/getRestoredStatusesToAuthorize.spec.js +50 -0
  460. package/dist/versions/getRestoredStatusesToAuthorize.spec.js.map +1 -0
  461. package/dist/versions/schedule/job.d.ts +2 -2
  462. package/dist/versions/schedule/job.d.ts.map +1 -1
  463. package/dist/versions/schedule/job.js +11 -7
  464. package/dist/versions/schedule/job.js.map +1 -1
  465. package/dist/versions/schedule/types.d.ts +5 -1
  466. package/dist/versions/schedule/types.d.ts.map +1 -1
  467. package/dist/versions/schedule/types.js.map +1 -1
  468. 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 { validateSvg } from './validateSvg.js';
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. For content validation (SVG safety, PDF integrity), the full buffer is
245
- // loaded lazily and only when the file type actually requires it.
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 small text-based types (SVG, PDF).
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
- // Secondary mimetype check to assess file type from buffer
272
- if (configMimeTypes.length > 0) {
273
- let detected;
274
- try {
275
- detected = isTempFile && tempFilePath ? await fileTypeFromFile(tempFilePath) : await fileTypeFromBuffer(file.data);
276
- } catch {
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
- const typeFromExtension = file.name.split('.').pop() || '';
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.some((ext)=>file.name.toLowerCase().endsWith(ext));
335
- const hasRestrictedMime = type.mimeType === file.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":"AAEA,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;AAED,eAAO,MAAM,qBAAqB,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAyC/D,CAAA"}
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
- if (await fileExists(fileToDelete)) {
13
- await fs.unlink(fileToDelete);
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
- if (await fileExists(sizeToDelete)) {
27
- await fs.unlink(sizeToDelete);
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 const fileToDelete = `${staticPath}/${doc.filename as string}`\n\n try {\n if (await fileExists(fileToDelete)) {\n await fs.unlink(fileToDelete)\n }\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 const sizeToDelete = `${staticPath}/${size.filename}`\n try {\n if (await fileExists(sizeToDelete)) {\n await fs.unlink(sizeToDelete)\n }\n } catch (ignore) {\n throw new ErrorDeletingFile(req.t)\n }\n }\n }\n }\n}\n"],"names":["fs","ErrorDeletingFile","fileExists","deleteAssociatedFiles","collectionConfig","doc","files","overrideDelete","req","upload","length","staticDir","staticPath","fileToDelete","filename","unlink","ignore","t","sizes","Object","values","size","sizeToDelete"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAO5B,SAASC,iBAAiB,QAAQ,qBAAoB;AACtD,SAASC,UAAU,QAAQ,kBAAiB;AAW5C,OAAO,MAAMC,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,WAAWC,UAAU,EAAE,GAAGR,iBAAiBK,MAAM;QAEzD,MAAMI,eAAe,GAAGD,WAAW,CAAC,EAAEP,IAAIS,QAAQ,EAAY;QAE9D,IAAI;YACF,IAAI,MAAMZ,WAAWW,eAAe;gBAClC,MAAMb,GAAGe,MAAM,CAACF;YAClB;QACF,EAAE,OAAOG,QAAQ;YACf,MAAM,IAAIf,kBAAkBO,IAAIS,CAAC;QACnC;QAEA,IAAIZ,IAAIa,KAAK,EAAE;YACb,MAAMA,QAAoBC,OAAOC,MAAM,CAACf,IAAIa,KAAK;YACjD,gEAAgE;YAChE,+DAA+D;YAC/D,wDAAwD;YAExD,KAAK,MAAMG,QAAQH,MAAO;gBACxB,MAAMI,eAAe,GAAGV,WAAW,CAAC,EAAES,KAAKP,QAAQ,EAAE;gBACrD,IAAI;oBACF,IAAI,MAAMZ,WAAWoB,eAAe;wBAClC,MAAMtB,GAAGe,MAAM,CAACO;oBAClB;gBACF,EAAE,OAAON,QAAQ;oBACf,MAAM,IAAIf,kBAAkBO,IAAIS,CAAC;gBACnC;YACF;QACF;IACF;AACF,EAAC"}
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,5 +1,3 @@
1
- /**
2
- * Securely detect if an XML buffer contains a valid SVG document
3
- */
1
+ /** Detect whether an XML document's root element is namespaced SVG. */
4
2
  export declare function detectSvgFromXml(buffer: Buffer): boolean;
5
3
  //# sourceMappingURL=detectSvgFromXml.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"detectSvgFromXml.d.ts","sourceRoot":"","sources":["../../src/uploads/detectSvgFromXml.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA6CxD"}
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"}