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
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getSanitizedUploadFilename, isXmlMimeType } from './getFileTypeIdentity.js';
3
+ describe('getSanitizedUploadFilename', ()=>{
4
+ it('should use the output extension while preserving the sanitized source basename', ()=>{
5
+ expect(getSanitizedUploadFilename('incoming/image.jpg', 'png')).toBe('image.png');
6
+ });
7
+ });
8
+ describe('isXmlMimeType', ()=>{
9
+ it.each([
10
+ [
11
+ 'application/xml',
12
+ true
13
+ ],
14
+ [
15
+ 'text/xml',
16
+ true
17
+ ],
18
+ [
19
+ 'application/atom+xml',
20
+ true
21
+ ],
22
+ [
23
+ 'image/svg+xml',
24
+ true
25
+ ],
26
+ [
27
+ 'APPLICATION/XML',
28
+ true
29
+ ],
30
+ [
31
+ ' Application/Atom+Xml; Charset=UTF-8 ',
32
+ true
33
+ ],
34
+ [
35
+ 'text/plain',
36
+ false
37
+ ]
38
+ ])('should classify %s as %s', (mimeType, expected)=>{
39
+ expect(isXmlMimeType(mimeType)).toBe(expected);
40
+ });
41
+ });
42
+
43
+ //# sourceMappingURL=getFileTypeIdentity.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/getFileTypeIdentity.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { getSanitizedUploadFilename, isXmlMimeType } from './getFileTypeIdentity.js'\n\ndescribe('getSanitizedUploadFilename', () => {\n it('should use the output extension while preserving the sanitized source basename', () => {\n expect(getSanitizedUploadFilename('incoming/image.jpg', 'png')).toBe('image.png')\n })\n})\n\ndescribe('isXmlMimeType', () => {\n it.each([\n ['application/xml', true],\n ['text/xml', true],\n ['application/atom+xml', true],\n ['image/svg+xml', true],\n ['APPLICATION/XML', true],\n [' Application/Atom+Xml; Charset=UTF-8 ', true],\n ['text/plain', false],\n ])('should classify %s as %s', (mimeType, expected) => {\n expect(isXmlMimeType(mimeType)).toBe(expected)\n })\n})\n"],"names":["describe","expect","it","getSanitizedUploadFilename","isXmlMimeType","toBe","each","mimeType","expected"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,0BAA0B,EAAEC,aAAa,QAAQ,2BAA0B;AAEpFJ,SAAS,8BAA8B;IACrCE,GAAG,kFAAkF;QACnFD,OAAOE,2BAA2B,sBAAsB,QAAQE,IAAI,CAAC;IACvE;AACF;AAEAL,SAAS,iBAAiB;IACxBE,GAAGI,IAAI,CAAC;QACN;YAAC;YAAmB;SAAK;QACzB;YAAC;YAAY;SAAK;QAClB;YAAC;YAAwB;SAAK;QAC9B;YAAC;YAAiB;SAAK;QACvB;YAAC;YAAmB;SAAK;QACzB;YAAC;YAAyC;SAAK;QAC/C;YAAC;YAAc;SAAM;KACtB,EAAE,4BAA4B,CAACC,UAAUC;QACxCP,OAAOG,cAAcG,WAAWF,IAAI,CAACG;IACvC;AACF"}
@@ -0,0 +1,2 @@
1
+ export declare function isProcessableImage(mimeType: string): boolean;
2
+ //# sourceMappingURL=isProcessableImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isProcessableImage.d.ts","sourceRoot":"","sources":["../../src/uploads/isProcessableImage.ts"],"names":[],"mappings":"AAGA,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D"}
@@ -0,0 +1,7 @@
1
+ import { canResizeImage } from './canResizeImage.js';
2
+ import { isImage } from './isImage.js';
3
+ export function isProcessableImage(mimeType) {
4
+ return isImage(mimeType) || canResizeImage(mimeType);
5
+ }
6
+
7
+ //# sourceMappingURL=isProcessableImage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/isProcessableImage.ts"],"sourcesContent":["import { canResizeImage } from './canResizeImage.js'\nimport { isImage } from './isImage.js'\n\nexport function isProcessableImage(mimeType: string): boolean {\n return isImage(mimeType) || canResizeImage(mimeType)\n}\n"],"names":["canResizeImage","isImage","isProcessableImage","mimeType"],"mappings":"AAAA,SAASA,cAAc,QAAQ,sBAAqB;AACpD,SAASC,OAAO,QAAQ,eAAc;AAEtC,OAAO,SAASC,mBAAmBC,QAAgB;IACjD,OAAOF,QAAQE,aAAaH,eAAeG;AAC7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"safeFetch.d.ts","sourceRoot":"","sources":["../../src/uploads/safeFetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AAEpD;;GAEG;AACH,eAAO,MAAM,yBAAyB;;CAErC,CAAA;AAgDD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,4FAAoD,OAAO,CAAC,QAAQ,CA4CzF,CAAA"}
1
+ {"version":3,"file":"safeFetch.d.ts","sourceRoot":"","sources":["../../src/uploads/safeFetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AAEpD;;GAEG;AACH,eAAO,MAAM,yBAAyB;;CAErC,CAAA;AAuDD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,4FAAoD,OAAO,CAAC,QAAQ,CA4CzF,CAAA"}
@@ -6,6 +6,7 @@ import { Agent, fetch as undiciFetch } from 'undici';
6
6
  */ export const _internal_safeFetchGlobal = {
7
7
  lookup
8
8
  };
9
+ const buildBlockedFetchError = (hostname)=>new Error(`Blocked unsafe attempt to ${hostname}. This host resolves to a private or internal address. ` + `If this is a trusted destination, allowlist it via the upload collection's "skipSafeFetch" option ` + `(https://payloadcms.com/docs/upload/overview#skip-safe-fetch).`);
9
10
  const isSafeIp = (ip)=>{
10
11
  try {
11
12
  if (!ip) {
@@ -39,7 +40,7 @@ const ssrfFilterInterceptor = (hostname, options, callback)=>{
39
40
  ];
40
41
  }
41
42
  if (ips.some((ip)=>!isSafeIp(ip))) {
42
- callback(new Error(`Blocked unsafe attempt to ${hostname}`), address, family);
43
+ callback(buildBlockedFetchError(hostname), address, family);
43
44
  return;
44
45
  }
45
46
  callback(null, address, family);
@@ -68,7 +69,7 @@ const safeDispatcher = new Agent({
68
69
  }
69
70
  if (ipaddr.isValid(hostname)) {
70
71
  if (!isSafeIp(hostname)) {
71
- throw new Error(`Blocked unsafe attempt to ${hostname}`);
72
+ throw buildBlockedFetchError(hostname);
72
73
  }
73
74
  }
74
75
  return await undiciFetch(url, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { LookupFunction } from 'net'\n\nimport { lookup } from 'dns'\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\n/**\n * @internal this is used to mock the IP `lookup` function in integration tests\n */\nexport const _internal_safeFetchGlobal = {\n lookup,\n}\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\nconst ssrfFilterInterceptor: LookupFunction = (hostname, options, callback) => {\n _internal_safeFetchGlobal.lookup(hostname, options, (err, address, family) => {\n if (err) {\n callback(err, address, family)\n } else {\n let ips = [] as string[]\n if (Array.isArray(address)) {\n ips = address.map((a) => a.address)\n } else {\n ips = [address]\n }\n\n if (ips.some((ip) => !isSafeIp(ip))) {\n callback(new Error(`Blocked unsafe attempt to ${hostname}`), address, family)\n return\n }\n\n callback(null, address, family)\n }\n })\n}\n\nconst safeDispatcher = new Agent({\n connect: { lookup: ssrfFilterInterceptor },\n})\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Validates domain names by resolving them to IP addresses and checking if they're safe.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>): Promise<Response> => {\n const [unverifiedUrl, options] = args\n\n try {\n const url = new URL(unverifiedUrl)\n\n let hostname = url.hostname\n\n // Strip brackets from IPv6 addresses (e.g., \"[::1]\" => \"::1\")\n if (hostname.startsWith('[') && hostname.endsWith(']')) {\n hostname = hostname.slice(1, -1)\n }\n\n if (ipaddr.isValid(hostname)) {\n if (!isSafeIp(hostname)) {\n throw new Error(`Blocked unsafe attempt to ${hostname}`)\n }\n }\n return (await undiciFetch(url, {\n ...options,\n dispatcher: safeDispatcher,\n redirect: 'manual', // Prevent automatic redirects\n })) as unknown as Response\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined = undefined\n if (typeof unverifiedUrl === 'string') {\n stringifiedUrl = unverifiedUrl\n } else if (unverifiedUrl instanceof URL) {\n stringifiedUrl = unverifiedUrl.toString()\n } else if (unverifiedUrl instanceof Request) {\n stringifiedUrl = unverifiedUrl.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["lookup","ipaddr","Agent","fetch","undiciFetch","_internal_safeFetchGlobal","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","ssrfFilterInterceptor","hostname","options","callback","err","address","family","ips","Array","isArray","map","a","some","Error","safeDispatcher","connect","safeFetch","args","unverifiedUrl","url","URL","startsWith","endsWith","slice","dispatcher","redirect","error","cause","message","includes","stringifiedUrl","undefined","toString","Request"],"mappings":"AAEA,SAASA,MAAM,QAAQ,MAAK;AAC5B,OAAOC,YAAY,YAAW;AAC9B,SAASC,KAAK,EAAEC,SAASC,WAAW,QAAQ,SAAQ;AAEpD;;CAEC,GACD,OAAO,MAAMC,4BAA4B;IACvCL;AACF,EAAC;AAED,MAAMM,WAAW,CAACC;IAChB,IAAI;QACF,IAAI,CAACA,IAAI;YACP,OAAO;QACT;QAEA,IAAI,CAACN,OAAOO,OAAO,CAACD,KAAK;YACvB,OAAO;QACT;QAEA,MAAME,kBAAkBR,OAAOS,KAAK,CAACH;QACrC,MAAMI,QAAQF,gBAAgBE,KAAK;QACnC,IAAIA,UAAU,WAAW;YACvB,OAAO,MAAM,mBAAmB;;QAClC;IACF,EAAE,OAAOC,QAAQ;QACf,OAAO;IACT;IACA,OAAO;AACT;AAEA,MAAMC,wBAAwC,CAACC,UAAUC,SAASC;IAChEX,0BAA0BL,MAAM,CAACc,UAAUC,SAAS,CAACE,KAAKC,SAASC;QACjE,IAAIF,KAAK;YACPD,SAASC,KAAKC,SAASC;QACzB,OAAO;YACL,IAAIC,MAAM,EAAE;YACZ,IAAIC,MAAMC,OAAO,CAACJ,UAAU;gBAC1BE,MAAMF,QAAQK,GAAG,CAAC,CAACC,IAAMA,EAAEN,OAAO;YACpC,OAAO;gBACLE,MAAM;oBAACF;iBAAQ;YACjB;YAEA,IAAIE,IAAIK,IAAI,CAAC,CAAClB,KAAO,CAACD,SAASC,MAAM;gBACnCS,SAAS,IAAIU,MAAM,CAAC,0BAA0B,EAAEZ,UAAU,GAAGI,SAASC;gBACtE;YACF;YAEAH,SAAS,MAAME,SAASC;QAC1B;IACF;AACF;AAEA,MAAMQ,iBAAiB,IAAIzB,MAAM;IAC/B0B,SAAS;QAAE5B,QAAQa;IAAsB;AAC3C;AACA;;;;;;CAMC,GACD,OAAO,MAAMgB,YAAY,OAAO,GAAGC;IACjC,MAAM,CAACC,eAAehB,QAAQ,GAAGe;IAEjC,IAAI;QACF,MAAME,MAAM,IAAIC,IAAIF;QAEpB,IAAIjB,WAAWkB,IAAIlB,QAAQ;QAE3B,8DAA8D;QAC9D,IAAIA,SAASoB,UAAU,CAAC,QAAQpB,SAASqB,QAAQ,CAAC,MAAM;YACtDrB,WAAWA,SAASsB,KAAK,CAAC,GAAG,CAAC;QAChC;QAEA,IAAInC,OAAOO,OAAO,CAACM,WAAW;YAC5B,IAAI,CAACR,SAASQ,WAAW;gBACvB,MAAM,IAAIY,MAAM,CAAC,0BAA0B,EAAEZ,UAAU;YACzD;QACF;QACA,OAAQ,MAAMV,YAAY4B,KAAK;YAC7B,GAAGjB,OAAO;YACVsB,YAAYV;YACZW,UAAU;QACZ;IACF,EAAE,OAAOC,OAAO;QACd,IAAIA,iBAAiBb,OAAO;YAC1B,IAAIa,MAAMC,KAAK,YAAYd,SAASa,MAAMC,KAAK,CAACC,OAAO,CAACC,QAAQ,CAAC,WAAW;gBAC1E,kFAAkF;gBAClF,2DAA2D;gBAC3D,MAAM,IAAIhB,MAAMa,MAAMC,KAAK,CAACC,OAAO;YACrC,OAAO;gBACL,IAAIE,iBAAqCC;gBACzC,IAAI,OAAOb,kBAAkB,UAAU;oBACrCY,iBAAiBZ;gBACnB,OAAO,IAAIA,yBAAyBE,KAAK;oBACvCU,iBAAiBZ,cAAcc,QAAQ;gBACzC,OAAO,IAAId,yBAAyBe,SAAS;oBAC3CH,iBAAiBZ,cAAcC,GAAG;gBACpC;gBAEA,MAAM,IAAIN,MAAM,CAAC,qBAAqB,EAAEiB,eAAe,EAAE,EAAEJ,MAAME,OAAO,EAAE;YAC5E;QACF;QACA,MAAMF;IACR;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { LookupFunction } from 'net'\n\nimport { lookup } from 'dns'\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\n/**\n * @internal this is used to mock the IP `lookup` function in integration tests\n */\nexport const _internal_safeFetchGlobal = {\n lookup,\n}\n\nconst buildBlockedFetchError = (hostname: string): Error =>\n new Error(\n `Blocked unsafe attempt to ${hostname}. This host resolves to a private or internal address. ` +\n `If this is a trusted destination, allowlist it via the upload collection's \"skipSafeFetch\" option ` +\n `(https://payloadcms.com/docs/upload/overview#skip-safe-fetch).`,\n )\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\nconst ssrfFilterInterceptor: LookupFunction = (hostname, options, callback) => {\n _internal_safeFetchGlobal.lookup(hostname, options, (err, address, family) => {\n if (err) {\n callback(err, address, family)\n } else {\n let ips = [] as string[]\n if (Array.isArray(address)) {\n ips = address.map((a) => a.address)\n } else {\n ips = [address]\n }\n\n if (ips.some((ip) => !isSafeIp(ip))) {\n callback(buildBlockedFetchError(hostname), address, family)\n return\n }\n\n callback(null, address, family)\n }\n })\n}\n\nconst safeDispatcher = new Agent({\n connect: { lookup: ssrfFilterInterceptor },\n})\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Validates domain names by resolving them to IP addresses and checking if they're safe.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>): Promise<Response> => {\n const [unverifiedUrl, options] = args\n\n try {\n const url = new URL(unverifiedUrl)\n\n let hostname = url.hostname\n\n // Strip brackets from IPv6 addresses (e.g., \"[::1]\" => \"::1\")\n if (hostname.startsWith('[') && hostname.endsWith(']')) {\n hostname = hostname.slice(1, -1)\n }\n\n if (ipaddr.isValid(hostname)) {\n if (!isSafeIp(hostname)) {\n throw buildBlockedFetchError(hostname)\n }\n }\n return (await undiciFetch(url, {\n ...options,\n dispatcher: safeDispatcher,\n redirect: 'manual', // Prevent automatic redirects\n })) as unknown as Response\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined = undefined\n if (typeof unverifiedUrl === 'string') {\n stringifiedUrl = unverifiedUrl\n } else if (unverifiedUrl instanceof URL) {\n stringifiedUrl = unverifiedUrl.toString()\n } else if (unverifiedUrl instanceof Request) {\n stringifiedUrl = unverifiedUrl.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["lookup","ipaddr","Agent","fetch","undiciFetch","_internal_safeFetchGlobal","buildBlockedFetchError","hostname","Error","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","ssrfFilterInterceptor","options","callback","err","address","family","ips","Array","isArray","map","a","some","safeDispatcher","connect","safeFetch","args","unverifiedUrl","url","URL","startsWith","endsWith","slice","dispatcher","redirect","error","cause","message","includes","stringifiedUrl","undefined","toString","Request"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { FlattenedField } from '../fields/config/types.js';
2
+ type Operation = 'create' | 'update';
3
+ export type ExternalUploadSource = {
4
+ filename: string;
5
+ url: string;
6
+ };
7
+ type UploadDataOptions = {
8
+ locale?: string;
9
+ localizedProperties?: Set<string>;
10
+ };
11
+ export declare const getExternalUploadSource: (data: unknown) => ExternalUploadSource | undefined;
12
+ export declare const getUploadDestination: ({ data, file, }: {
13
+ data: unknown;
14
+ file: {
15
+ clientUploadContext?: unknown;
16
+ } | null | undefined;
17
+ }) => {
18
+ objectKey?: string;
19
+ prefix?: string;
20
+ };
21
+ export declare const getLocalizedUploadProperties: (fields: FlattenedField[]) => Set<string>;
22
+ export declare const sanitizeUploadData: <T>(data: T, operation: Operation) => T;
23
+ export declare const mergeUploadDataWithDocument: <T>(data: T, document: unknown, options?: UploadDataOptions) => T;
24
+ export declare const restoreUploadDataFromDocument: <T>(data: T, document: unknown, options?: UploadDataOptions) => T;
25
+ export {};
26
+ //# sourceMappingURL=sanitizeUploadData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeUploadData.d.ts","sourceRoot":"","sources":["../../src/uploads/sanitizeUploadData.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAI/D,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEpC,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAClC,CAAA;AAQD,eAAO,MAAM,uBAAuB,SAAU,OAAO,KAAG,oBAAoB,GAAG,SAS9E,CAAA;AAKD,eAAO,MAAM,oBAAoB,oBAG9B;IACD,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAA;CAC3D,KAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAoBxC,CAAA;AAcD,eAAO,MAAM,4BAA4B,WAAY,cAAc,EAAE,KAAG,GAAG,CAAC,MAAM,CAUjF,CAAA;AAqBD,eAAO,MAAM,kBAAkB,GAAI,CAAC,QAAQ,CAAC,aAAa,SAAS,KAAG,CA4BrE,CAAA;AAQD,eAAO,MAAM,2BAA2B,GAAI,CAAC,QACrC,CAAC,YACG,OAAO,YACR,iBAAiB,KACzB,CA8BF,CAAA;AAiBD,eAAO,MAAM,6BAA6B,GAAI,CAAC,QACvC,CAAC,YACG,OAAO,YACR,iBAAiB,KACzB,CAeF,CAAA"}
@@ -0,0 +1,159 @@
1
+ import path from 'path';
2
+ import { APIError } from '../errors/APIError.js';
3
+ const hasOwnProperty = (value, key)=>Object.prototype.hasOwnProperty.call(value, key);
4
+ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
5
+ export const getExternalUploadSource = (data)=>{
6
+ if (!isRecord(data) || typeof data.filename !== 'string' || typeof data.url !== 'string') {
7
+ return undefined;
8
+ }
9
+ return {
10
+ filename: data.filename,
11
+ url: data.url
12
+ };
13
+ };
14
+ // Resolves the server-owned upload destination for a document write. For client uploads the prefix
15
+ // and key segment come from the verified upload context; for server uploads the prefix falls back
16
+ // to the submitted document data and there is no key segment.
17
+ export const getUploadDestination = ({ data, file })=>{
18
+ if (!file) {
19
+ return {};
20
+ }
21
+ const submittedPrefix = isRecord(data) && typeof data.prefix === 'string' ? data.prefix : undefined;
22
+ const { clientUploadContext } = file;
23
+ if (!isRecord(clientUploadContext)) {
24
+ return {
25
+ prefix: submittedPrefix
26
+ };
27
+ }
28
+ return {
29
+ objectKey: typeof clientUploadContext._objectKey === 'string' ? clientUploadContext._objectKey : undefined,
30
+ prefix: typeof clientUploadContext.prefix === 'string' ? clientUploadContext.prefix : undefined
31
+ };
32
+ };
33
+ const getDocumentProperty = (document, property, { locale, localizedProperties })=>{
34
+ const value = document[property];
35
+ return locale && localizedProperties?.has(property) && isRecord(value) ? value[locale] : value;
36
+ };
37
+ // Accepts flattened fields so localized identity fields declared inside presentational
38
+ // containers (row/collapsible/unnamed tabs) are still detected — those live at the document
39
+ // root data level but are nested in the config tree, so walking `fields` alone would miss them.
40
+ export const getLocalizedUploadProperties = (fields)=>{
41
+ const localizedProperties = new Set();
42
+ for (const field of fields){
43
+ if ('name' in field && field.localized) {
44
+ localizedProperties.add(field.name);
45
+ }
46
+ }
47
+ return localizedProperties;
48
+ };
49
+ // The submitted filename is stripped by sanitizeUploadData regardless, so this only exists to
50
+ // reject path-traversal attempts loudly (400) instead of silently dropping them. Non-string
51
+ // values are ignored here because they are removed by the caller anyway.
52
+ const validateFilename = (filename)=>{
53
+ if (typeof filename !== 'string') {
54
+ return;
55
+ }
56
+ const pathSegments = filename.replace(/\\/g, '/').split('/');
57
+ if (path.posix.isAbsolute(filename) || path.win32.isAbsolute(filename) || pathSegments.includes('..')) {
58
+ throw new APIError('Invalid filename.', 400);
59
+ }
60
+ };
61
+ export const sanitizeUploadData = (data, operation)=>{
62
+ if (!isRecord(data)) {
63
+ return data;
64
+ }
65
+ validateFilename(data.filename);
66
+ if (isRecord(data.sizes)) {
67
+ for (const sizeData of Object.values(data.sizes)){
68
+ if (isRecord(sizeData)) {
69
+ validateFilename(sizeData.filename);
70
+ }
71
+ }
72
+ }
73
+ const sanitizedData = {
74
+ ...data
75
+ };
76
+ delete sanitizedData.filename;
77
+ delete sanitizedData.sizes;
78
+ delete sanitizedData.url;
79
+ // Server-owned; never accepted from the caller.
80
+ delete sanitizedData._objectKey;
81
+ // On update, `prefix` is restored from the stored document (file identity); kept on create.
82
+ if (operation === 'update') {
83
+ delete sanitizedData.prefix;
84
+ }
85
+ return sanitizedData;
86
+ };
87
+ // Restores only the identity fields that select/serve a stored object (`filename`, `prefix`,
88
+ // `url`, `sizes`). Other generated-but-derived fields (`thumbnailURL`, `filesize`, `height`,
89
+ // `width`, `mimeType`, `focalX`/`focalY`) are intentionally left as submitted: `focalX`/`focalY`
90
+ // are user-editable crop input, and the rest are regenerated by generateFileData when a file is
91
+ // written. Compare with restoreUploadDataFromDocument (version restore), which re-binds the full
92
+ // tuple below.
93
+ export const mergeUploadDataWithDocument = (data, document, options = {})=>{
94
+ if (!isRecord(data) || !isRecord(document)) {
95
+ return data;
96
+ }
97
+ const mergedData = {
98
+ ...data
99
+ };
100
+ for (const property of [
101
+ '_objectKey',
102
+ 'filename',
103
+ 'prefix',
104
+ 'url'
105
+ ]){
106
+ if (!hasOwnProperty(data, property) && hasOwnProperty(document, property)) {
107
+ mergedData[property] = getDocumentProperty(document, property, options);
108
+ }
109
+ }
110
+ const documentSizes = getDocumentProperty(document, 'sizes', options);
111
+ if (!hasOwnProperty(data, 'sizes') && hasOwnProperty(document, 'sizes')) {
112
+ mergedData.sizes = documentSizes;
113
+ } else if (isRecord(data.sizes) && isRecord(documentSizes)) {
114
+ const mergedSizes = {
115
+ ...documentSizes
116
+ };
117
+ for (const [sizeName, sizeData] of Object.entries(data.sizes)){
118
+ const documentSize = documentSizes[sizeName];
119
+ mergedSizes[sizeName] = isRecord(sizeData) && isRecord(documentSize) ? {
120
+ ...documentSize,
121
+ ...sizeData
122
+ } : sizeData;
123
+ }
124
+ mergedData.sizes = mergedSizes;
125
+ }
126
+ return mergedData;
127
+ };
128
+ const uploadDerivedProperties = [
129
+ '_objectKey',
130
+ 'filename',
131
+ 'filesize',
132
+ 'focalX',
133
+ 'focalY',
134
+ 'height',
135
+ 'mimeType',
136
+ 'prefix',
137
+ 'sizes',
138
+ 'thumbnailURL',
139
+ 'url',
140
+ 'width'
141
+ ];
142
+ export const restoreUploadDataFromDocument = (data, document, options = {})=>{
143
+ if (!isRecord(data) || !isRecord(document)) {
144
+ return data;
145
+ }
146
+ const restoredData = {
147
+ ...data
148
+ };
149
+ for (const property of uploadDerivedProperties){
150
+ if (hasOwnProperty(document, property)) {
151
+ restoredData[property] = getDocumentProperty(document, property, options);
152
+ } else {
153
+ delete restoredData[property];
154
+ }
155
+ }
156
+ return restoredData;
157
+ };
158
+
159
+ //# sourceMappingURL=sanitizeUploadData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/sanitizeUploadData.ts"],"sourcesContent":["import path from 'path'\n\nimport type { FlattenedField } from '../fields/config/types.js'\n\nimport { APIError } from '../errors/APIError.js'\n\ntype Operation = 'create' | 'update'\n\nexport type ExternalUploadSource = {\n filename: string\n url: string\n}\n\ntype UploadDataOptions = {\n locale?: string\n localizedProperties?: Set<string>\n}\n\nconst hasOwnProperty = (value: Record<string, unknown>, key: string): boolean =>\n Object.prototype.hasOwnProperty.call(value, key)\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nexport const getExternalUploadSource = (data: unknown): ExternalUploadSource | undefined => {\n if (!isRecord(data) || typeof data.filename !== 'string' || typeof data.url !== 'string') {\n return undefined\n }\n\n return {\n filename: data.filename,\n url: data.url,\n }\n}\n\n// Resolves the server-owned upload destination for a document write. For client uploads the prefix\n// and key segment come from the verified upload context; for server uploads the prefix falls back\n// to the submitted document data and there is no key segment.\nexport const getUploadDestination = ({\n data,\n file,\n}: {\n data: unknown\n file: { clientUploadContext?: unknown } | null | undefined\n}): { objectKey?: string; prefix?: string } => {\n if (!file) {\n return {}\n }\n\n const submittedPrefix =\n isRecord(data) && typeof data.prefix === 'string' ? data.prefix : undefined\n const { clientUploadContext } = file\n\n if (!isRecord(clientUploadContext)) {\n return { prefix: submittedPrefix }\n }\n\n return {\n objectKey:\n typeof clientUploadContext._objectKey === 'string'\n ? clientUploadContext._objectKey\n : undefined,\n prefix: typeof clientUploadContext.prefix === 'string' ? clientUploadContext.prefix : undefined,\n }\n}\n\nconst getDocumentProperty = (\n document: Record<string, unknown>,\n property: string,\n { locale, localizedProperties }: UploadDataOptions,\n): unknown => {\n const value = document[property]\n return locale && localizedProperties?.has(property) && isRecord(value) ? value[locale] : value\n}\n\n// Accepts flattened fields so localized identity fields declared inside presentational\n// containers (row/collapsible/unnamed tabs) are still detected — those live at the document\n// root data level but are nested in the config tree, so walking `fields` alone would miss them.\nexport const getLocalizedUploadProperties = (fields: FlattenedField[]): Set<string> => {\n const localizedProperties = new Set<string>()\n\n for (const field of fields) {\n if ('name' in field && (field as { localized?: boolean }).localized) {\n localizedProperties.add(field.name)\n }\n }\n\n return localizedProperties\n}\n\n// The submitted filename is stripped by sanitizeUploadData regardless, so this only exists to\n// reject path-traversal attempts loudly (400) instead of silently dropping them. Non-string\n// values are ignored here because they are removed by the caller anyway.\nconst validateFilename = (filename: unknown): void => {\n if (typeof filename !== 'string') {\n return\n }\n\n const pathSegments = filename.replace(/\\\\/g, '/').split('/')\n\n if (\n path.posix.isAbsolute(filename) ||\n path.win32.isAbsolute(filename) ||\n pathSegments.includes('..')\n ) {\n throw new APIError('Invalid filename.', 400)\n }\n}\n\nexport const sanitizeUploadData = <T>(data: T, operation: Operation): T => {\n if (!isRecord(data)) {\n return data\n }\n\n validateFilename(data.filename)\n\n if (isRecord(data.sizes)) {\n for (const sizeData of Object.values(data.sizes)) {\n if (isRecord(sizeData)) {\n validateFilename(sizeData.filename)\n }\n }\n }\n\n const sanitizedData: Record<string, unknown> = { ...data }\n delete sanitizedData.filename\n delete sanitizedData.sizes\n delete sanitizedData.url\n // Server-owned; never accepted from the caller.\n delete sanitizedData._objectKey\n\n // On update, `prefix` is restored from the stored document (file identity); kept on create.\n if (operation === 'update') {\n delete sanitizedData.prefix\n }\n\n return sanitizedData as T\n}\n\n// Restores only the identity fields that select/serve a stored object (`filename`, `prefix`,\n// `url`, `sizes`). Other generated-but-derived fields (`thumbnailURL`, `filesize`, `height`,\n// `width`, `mimeType`, `focalX`/`focalY`) are intentionally left as submitted: `focalX`/`focalY`\n// are user-editable crop input, and the rest are regenerated by generateFileData when a file is\n// written. Compare with restoreUploadDataFromDocument (version restore), which re-binds the full\n// tuple below.\nexport const mergeUploadDataWithDocument = <T>(\n data: T,\n document: unknown,\n options: UploadDataOptions = {},\n): T => {\n if (!isRecord(data) || !isRecord(document)) {\n return data\n }\n\n const mergedData: Record<string, unknown> = { ...data }\n\n for (const property of ['_objectKey', 'filename', 'prefix', 'url']) {\n if (!hasOwnProperty(data, property) && hasOwnProperty(document, property)) {\n mergedData[property] = getDocumentProperty(document, property, options)\n }\n }\n\n const documentSizes = getDocumentProperty(document, 'sizes', options)\n if (!hasOwnProperty(data, 'sizes') && hasOwnProperty(document, 'sizes')) {\n mergedData.sizes = documentSizes\n } else if (isRecord(data.sizes) && isRecord(documentSizes)) {\n const mergedSizes: Record<string, unknown> = { ...documentSizes }\n\n for (const [sizeName, sizeData] of Object.entries(data.sizes)) {\n const documentSize = documentSizes[sizeName]\n\n mergedSizes[sizeName] =\n isRecord(sizeData) && isRecord(documentSize) ? { ...documentSize, ...sizeData } : sizeData\n }\n\n mergedData.sizes = mergedSizes\n }\n\n return mergedData as T\n}\n\nconst uploadDerivedProperties = [\n '_objectKey',\n 'filename',\n 'filesize',\n 'focalX',\n 'focalY',\n 'height',\n 'mimeType',\n 'prefix',\n 'sizes',\n 'thumbnailURL',\n 'url',\n 'width',\n] as const\n\nexport const restoreUploadDataFromDocument = <T>(\n data: T,\n document: unknown,\n options: UploadDataOptions = {},\n): T => {\n if (!isRecord(data) || !isRecord(document)) {\n return data\n }\n\n const restoredData: Record<string, unknown> = { ...data }\n for (const property of uploadDerivedProperties) {\n if (hasOwnProperty(document, property)) {\n restoredData[property] = getDocumentProperty(document, property, options)\n } else {\n delete restoredData[property]\n }\n }\n\n return restoredData as T\n}\n"],"names":["path","APIError","hasOwnProperty","value","key","Object","prototype","call","isRecord","Array","isArray","getExternalUploadSource","data","filename","url","undefined","getUploadDestination","file","submittedPrefix","prefix","clientUploadContext","objectKey","_objectKey","getDocumentProperty","document","property","locale","localizedProperties","has","getLocalizedUploadProperties","fields","Set","field","localized","add","name","validateFilename","pathSegments","replace","split","posix","isAbsolute","win32","includes","sanitizeUploadData","operation","sizes","sizeData","values","sanitizedData","mergeUploadDataWithDocument","options","mergedData","documentSizes","mergedSizes","sizeName","entries","documentSize","uploadDerivedProperties","restoreUploadDataFromDocument","restoredData"],"mappings":"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"}
@@ -0,0 +1,255 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getExternalUploadSource, getLocalizedUploadProperties, getUploadDestination, mergeUploadDataWithDocument, restoreUploadDataFromDocument, sanitizeUploadData } from './sanitizeUploadData.js';
3
+ import { flattenAllFields } from '../utilities/flattenAllFields.js';
4
+ describe('sanitizeUploadData', ()=>{
5
+ it('should preserve only the temporary external upload source', ()=>{
6
+ expect(getExternalUploadSource({
7
+ alt: 'Alternative text',
8
+ filename: 'remote.png',
9
+ sizes: {
10
+ thumbnail: {
11
+ filename: 'submitted-thumbnail.png'
12
+ }
13
+ },
14
+ url: 'https://example.com/remote.png'
15
+ })).toEqual({
16
+ filename: 'remote.png',
17
+ url: 'https://example.com/remote.png'
18
+ });
19
+ });
20
+ it('should remove submitted generated file data on update', ()=>{
21
+ expect(sanitizeUploadData({
22
+ alt: 'Updated alternative text',
23
+ filename: 'submitted.png',
24
+ prefix: 'articles',
25
+ sizes: {
26
+ thumbnail: {
27
+ filename: 'submitted-thumbnail.png',
28
+ height: 100,
29
+ width: 100
30
+ }
31
+ },
32
+ url: '/api/media/file/submitted.png'
33
+ }, 'update')).toEqual({
34
+ alt: 'Updated alternative text'
35
+ });
36
+ });
37
+ it('should preserve a submitted prefix on create', ()=>{
38
+ expect(sanitizeUploadData({
39
+ filename: 'submitted.png',
40
+ prefix: 'articles'
41
+ }, 'create')).toEqual({
42
+ prefix: 'articles'
43
+ });
44
+ });
45
+ it('should use a submitted prefix when writing a new file', ()=>{
46
+ expect(getUploadDestination({
47
+ data: {
48
+ prefix: 'articles'
49
+ },
50
+ file: {}
51
+ })).toEqual({
52
+ prefix: 'articles'
53
+ });
54
+ });
55
+ it('should use the destination bound to a client upload context', ()=>{
56
+ expect(getUploadDestination({
57
+ data: {
58
+ prefix: 'submitted'
59
+ },
60
+ file: {
61
+ clientUploadContext: {
62
+ _objectKey: 'abc123',
63
+ prefix: 'provider'
64
+ }
65
+ }
66
+ })).toEqual({
67
+ objectKey: 'abc123',
68
+ prefix: 'provider'
69
+ });
70
+ expect(getUploadDestination({
71
+ data: {
72
+ prefix: 'submitted'
73
+ },
74
+ file: {
75
+ clientUploadContext: {
76
+ key: 'provider/file.png'
77
+ }
78
+ }
79
+ })).toEqual({});
80
+ });
81
+ it('should not preserve a submitted prefix without a new file', ()=>{
82
+ expect(getUploadDestination({
83
+ data: {
84
+ prefix: 'articles'
85
+ },
86
+ file: undefined
87
+ })).toEqual({});
88
+ });
89
+ it.each([
90
+ {
91
+ filename: '../submitted.png'
92
+ },
93
+ {
94
+ filename: '/submitted.png'
95
+ },
96
+ {
97
+ filename: 'C:\\submitted.png'
98
+ },
99
+ {
100
+ filename: '\\\\server\\submitted.png'
101
+ },
102
+ {
103
+ filename: 'images/../submitted.png'
104
+ },
105
+ {
106
+ sizes: {
107
+ thumbnail: {
108
+ filename: '..\\submitted-thumbnail.png'
109
+ }
110
+ }
111
+ }
112
+ ])('should reject an invalid submitted filename', (data)=>{
113
+ expect(()=>sanitizeUploadData(data, 'update')).toThrow('Invalid filename.');
114
+ });
115
+ });
116
+ describe('mergeUploadDataWithDocument', ()=>{
117
+ it('should add stored generated file data to an update', ()=>{
118
+ expect(mergeUploadDataWithDocument({
119
+ alt: 'Updated alternative text'
120
+ }, {
121
+ filename: 'stored.png',
122
+ prefix: 'articles',
123
+ sizes: {
124
+ thumbnail: {
125
+ filename: 'stored-thumbnail.png',
126
+ height: 100,
127
+ width: 100
128
+ }
129
+ },
130
+ url: '/api/media/file/stored.png'
131
+ })).toEqual({
132
+ alt: 'Updated alternative text',
133
+ filename: 'stored.png',
134
+ prefix: 'articles',
135
+ sizes: {
136
+ thumbnail: {
137
+ filename: 'stored-thumbnail.png',
138
+ height: 100,
139
+ width: 100
140
+ }
141
+ },
142
+ url: '/api/media/file/stored.png'
143
+ });
144
+ });
145
+ it('should preserve generated file data supplied by a trusted hook', ()=>{
146
+ expect(mergeUploadDataWithDocument({
147
+ filename: 'hook.png',
148
+ prefix: 'hook-prefix',
149
+ sizes: {
150
+ thumbnail: {
151
+ filename: 'hook-thumbnail.png'
152
+ }
153
+ },
154
+ url: '/api/media/file/hook.png'
155
+ }, {
156
+ filename: 'stored.png',
157
+ prefix: 'stored-prefix',
158
+ sizes: {
159
+ thumbnail: {
160
+ filename: 'stored-thumbnail.png',
161
+ height: 100,
162
+ width: 100
163
+ }
164
+ },
165
+ url: '/api/media/file/stored.png'
166
+ })).toEqual({
167
+ filename: 'hook.png',
168
+ prefix: 'hook-prefix',
169
+ sizes: {
170
+ thumbnail: {
171
+ filename: 'hook-thumbnail.png',
172
+ height: 100,
173
+ width: 100
174
+ }
175
+ },
176
+ url: '/api/media/file/hook.png'
177
+ });
178
+ });
179
+ it('should merge the selected value for localized generated file data', ()=>{
180
+ expect(mergeUploadDataWithDocument({
181
+ alt: 'Updated alternative text'
182
+ }, {
183
+ prefix: {
184
+ en: 'current-prefix',
185
+ es: 'prefijo-actual'
186
+ }
187
+ }, {
188
+ locale: 'en',
189
+ localizedProperties: new Set([
190
+ 'prefix'
191
+ ])
192
+ })).toEqual({
193
+ alt: 'Updated alternative text',
194
+ prefix: 'current-prefix'
195
+ });
196
+ });
197
+ });
198
+ describe('getLocalizedUploadProperties', ()=>{
199
+ it('should identify localized flattened fields', ()=>{
200
+ expect(getLocalizedUploadProperties(flattenAllFields({
201
+ fields: [
202
+ {
203
+ fields: [
204
+ {
205
+ localized: true,
206
+ name: 'prefix',
207
+ type: 'text'
208
+ },
209
+ {
210
+ name: 'url',
211
+ type: 'text'
212
+ }
213
+ ],
214
+ type: 'row'
215
+ }
216
+ ]
217
+ }))).toEqual(new Set([
218
+ 'prefix'
219
+ ]));
220
+ });
221
+ });
222
+ describe('restoreUploadDataFromDocument', ()=>{
223
+ it('should replace the complete generated file tuple and preserve missing values', ()=>{
224
+ expect(restoreUploadDataFromDocument({
225
+ filename: 'version.png',
226
+ focalX: 10,
227
+ thumbnailURL: '/version-thumbnail.png'
228
+ }, {
229
+ filename: 'current.png',
230
+ thumbnailURL: null
231
+ })).toEqual({
232
+ filename: 'current.png',
233
+ thumbnailURL: null
234
+ });
235
+ });
236
+ it('should restore the selected value for localized generated file data', ()=>{
237
+ expect(restoreUploadDataFromDocument({
238
+ prefix: 'version-prefix'
239
+ }, {
240
+ prefix: {
241
+ en: 'current-prefix',
242
+ es: 'prefijo-actual'
243
+ }
244
+ }, {
245
+ locale: 'en',
246
+ localizedProperties: new Set([
247
+ 'prefix'
248
+ ])
249
+ })).toEqual({
250
+ prefix: 'current-prefix'
251
+ });
252
+ });
253
+ });
254
+
255
+ //# sourceMappingURL=sanitizeUploadData.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/sanitizeUploadData.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport {\n getExternalUploadSource,\n getLocalizedUploadProperties,\n getUploadDestination,\n mergeUploadDataWithDocument,\n restoreUploadDataFromDocument,\n sanitizeUploadData,\n} from './sanitizeUploadData.js'\nimport { flattenAllFields } from '../utilities/flattenAllFields.js'\n\ndescribe('sanitizeUploadData', () => {\n it('should preserve only the temporary external upload source', () => {\n expect(\n getExternalUploadSource({\n alt: 'Alternative text',\n filename: 'remote.png',\n sizes: { thumbnail: { filename: 'submitted-thumbnail.png' } },\n url: 'https://example.com/remote.png',\n }),\n ).toEqual({\n filename: 'remote.png',\n url: 'https://example.com/remote.png',\n })\n })\n\n it('should remove submitted generated file data on update', () => {\n expect(\n sanitizeUploadData(\n {\n alt: 'Updated alternative text',\n filename: 'submitted.png',\n prefix: 'articles',\n sizes: {\n thumbnail: {\n filename: 'submitted-thumbnail.png',\n height: 100,\n width: 100,\n },\n },\n url: '/api/media/file/submitted.png',\n },\n 'update',\n ),\n ).toEqual({\n alt: 'Updated alternative text',\n })\n })\n\n it('should preserve a submitted prefix on create', () => {\n expect(\n sanitizeUploadData(\n {\n filename: 'submitted.png',\n prefix: 'articles',\n },\n 'create',\n ),\n ).toEqual({\n prefix: 'articles',\n })\n })\n\n it('should use a submitted prefix when writing a new file', () => {\n expect(getUploadDestination({ data: { prefix: 'articles' }, file: {} })).toEqual({\n prefix: 'articles',\n })\n })\n\n it('should use the destination bound to a client upload context', () => {\n expect(\n getUploadDestination({\n data: { prefix: 'submitted' },\n file: { clientUploadContext: { _objectKey: 'abc123', prefix: 'provider' } },\n }),\n ).toEqual({ objectKey: 'abc123', prefix: 'provider' })\n expect(\n getUploadDestination({\n data: { prefix: 'submitted' },\n file: { clientUploadContext: { key: 'provider/file.png' } },\n }),\n ).toEqual({})\n })\n\n it('should not preserve a submitted prefix without a new file', () => {\n expect(getUploadDestination({ data: { prefix: 'articles' }, file: undefined })).toEqual({})\n })\n\n it.each([\n { filename: '../submitted.png' },\n { filename: '/submitted.png' },\n { filename: 'C:\\\\submitted.png' },\n { filename: '\\\\\\\\server\\\\submitted.png' },\n { filename: 'images/../submitted.png' },\n { sizes: { thumbnail: { filename: '..\\\\submitted-thumbnail.png' } } },\n ])('should reject an invalid submitted filename', (data) => {\n expect(() => sanitizeUploadData(data, 'update')).toThrow('Invalid filename.')\n })\n})\n\ndescribe('mergeUploadDataWithDocument', () => {\n it('should add stored generated file data to an update', () => {\n expect(\n mergeUploadDataWithDocument(\n { alt: 'Updated alternative text' },\n {\n filename: 'stored.png',\n prefix: 'articles',\n sizes: {\n thumbnail: {\n filename: 'stored-thumbnail.png',\n height: 100,\n width: 100,\n },\n },\n url: '/api/media/file/stored.png',\n },\n ),\n ).toEqual({\n alt: 'Updated alternative text',\n filename: 'stored.png',\n prefix: 'articles',\n sizes: {\n thumbnail: {\n filename: 'stored-thumbnail.png',\n height: 100,\n width: 100,\n },\n },\n url: '/api/media/file/stored.png',\n })\n })\n\n it('should preserve generated file data supplied by a trusted hook', () => {\n expect(\n mergeUploadDataWithDocument(\n {\n filename: 'hook.png',\n prefix: 'hook-prefix',\n sizes: { thumbnail: { filename: 'hook-thumbnail.png' } },\n url: '/api/media/file/hook.png',\n },\n {\n filename: 'stored.png',\n prefix: 'stored-prefix',\n sizes: {\n thumbnail: { filename: 'stored-thumbnail.png', height: 100, width: 100 },\n },\n url: '/api/media/file/stored.png',\n },\n ),\n ).toEqual({\n filename: 'hook.png',\n prefix: 'hook-prefix',\n sizes: {\n thumbnail: { filename: 'hook-thumbnail.png', height: 100, width: 100 },\n },\n url: '/api/media/file/hook.png',\n })\n })\n\n it('should merge the selected value for localized generated file data', () => {\n expect(\n mergeUploadDataWithDocument(\n { alt: 'Updated alternative text' },\n { prefix: { en: 'current-prefix', es: 'prefijo-actual' } },\n { locale: 'en', localizedProperties: new Set(['prefix']) },\n ),\n ).toEqual({ alt: 'Updated alternative text', prefix: 'current-prefix' })\n })\n})\n\ndescribe('getLocalizedUploadProperties', () => {\n it('should identify localized flattened fields', () => {\n expect(\n getLocalizedUploadProperties(\n flattenAllFields({\n fields: [\n {\n fields: [\n { localized: true, name: 'prefix', type: 'text' },\n { name: 'url', type: 'text' },\n ],\n type: 'row',\n },\n ],\n }),\n ),\n ).toEqual(new Set(['prefix']))\n })\n})\n\ndescribe('restoreUploadDataFromDocument', () => {\n it('should replace the complete generated file tuple and preserve missing values', () => {\n expect(\n restoreUploadDataFromDocument(\n { filename: 'version.png', focalX: 10, thumbnailURL: '/version-thumbnail.png' },\n { filename: 'current.png', thumbnailURL: null },\n ),\n ).toEqual({ filename: 'current.png', thumbnailURL: null })\n })\n\n it('should restore the selected value for localized generated file data', () => {\n expect(\n restoreUploadDataFromDocument(\n { prefix: 'version-prefix' },\n { prefix: { en: 'current-prefix', es: 'prefijo-actual' } },\n { locale: 'en', localizedProperties: new Set(['prefix']) },\n ),\n ).toEqual({ prefix: 'current-prefix' })\n })\n})\n"],"names":["describe","expect","it","getExternalUploadSource","getLocalizedUploadProperties","getUploadDestination","mergeUploadDataWithDocument","restoreUploadDataFromDocument","sanitizeUploadData","flattenAllFields","alt","filename","sizes","thumbnail","url","toEqual","prefix","height","width","data","file","clientUploadContext","_objectKey","objectKey","key","undefined","each","toThrow","en","es","locale","localizedProperties","Set","fields","localized","name","type","focalX","thumbnailURL"],"mappings":"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"}