payload 3.89.0 → 3.90.1

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 (459) 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 +118 -18
  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 +87 -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 +102 -24
  255. package/dist/index.d.ts +3 -1
  256. package/dist/index.d.ts.map +1 -1
  257. package/dist/index.js +2 -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/types/index.d.ts +2 -1
  266. package/dist/queues/config/types/index.d.ts.map +1 -1
  267. package/dist/queues/config/types/index.js.map +1 -1
  268. package/dist/uploads/assertClientUploadAccess.d.ts +7 -0
  269. package/dist/uploads/assertClientUploadAccess.d.ts.map +1 -0
  270. package/dist/uploads/assertClientUploadAccess.js +27 -0
  271. package/dist/uploads/assertClientUploadAccess.js.map +1 -0
  272. package/dist/uploads/assertClientUploadAllowed.d.ts +11 -0
  273. package/dist/uploads/assertClientUploadAllowed.d.ts.map +1 -0
  274. package/dist/uploads/assertClientUploadAllowed.js +46 -0
  275. package/dist/uploads/assertClientUploadAllowed.js.map +1 -0
  276. package/dist/uploads/assertClientUploadAllowed.spec.js +91 -0
  277. package/dist/uploads/assertClientUploadAllowed.spec.js.map +1 -0
  278. package/dist/uploads/checkFileRestrictions.d.ts +4 -1
  279. package/dist/uploads/checkFileRestrictions.d.ts.map +1 -1
  280. package/dist/uploads/checkFileRestrictions.js +66 -26
  281. package/dist/uploads/checkFileRestrictions.js.map +1 -1
  282. package/dist/uploads/clientUploadReceipt.d.ts +23 -0
  283. package/dist/uploads/clientUploadReceipt.d.ts.map +1 -0
  284. package/dist/uploads/clientUploadReceipt.js +55 -0
  285. package/dist/uploads/clientUploadReceipt.js.map +1 -0
  286. package/dist/uploads/deleteAssociatedFiles.d.ts.map +1 -1
  287. package/dist/uploads/deleteAssociatedFiles.js +33 -8
  288. package/dist/uploads/deleteAssociatedFiles.js.map +1 -1
  289. package/dist/uploads/deleteAssociatedFiles.spec.js +124 -0
  290. package/dist/uploads/deleteAssociatedFiles.spec.js.map +1 -0
  291. package/dist/uploads/detectSvgFromXml.d.ts +1 -3
  292. package/dist/uploads/detectSvgFromXml.d.ts.map +1 -1
  293. package/dist/uploads/detectSvgFromXml.js +3 -40
  294. package/dist/uploads/detectSvgFromXml.js.map +1 -1
  295. package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
  296. package/dist/uploads/endpoints/getFile.js +5 -3
  297. package/dist/uploads/endpoints/getFile.js.map +1 -1
  298. package/dist/uploads/fetchAPI-multipart/handlers.d.ts +2 -2
  299. package/dist/uploads/fetchAPI-multipart/handlers.d.ts.map +1 -1
  300. package/dist/uploads/fetchAPI-multipart/handlers.js +18 -5
  301. package/dist/uploads/fetchAPI-multipart/handlers.js.map +1 -1
  302. package/dist/uploads/fetchAPI-multipart/index.d.ts.map +1 -1
  303. package/dist/uploads/fetchAPI-multipart/index.js +24 -2
  304. package/dist/uploads/fetchAPI-multipart/index.js.map +1 -1
  305. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts +0 -6
  306. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts.map +1 -1
  307. package/dist/uploads/fetchAPI-multipart/processMultipart.js +146 -126
  308. package/dist/uploads/fetchAPI-multipart/processMultipart.js.map +1 -1
  309. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js +663 -0
  310. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js.map +1 -0
  311. package/dist/uploads/generateFileData.d.ts +3 -1
  312. package/dist/uploads/generateFileData.d.ts.map +1 -1
  313. package/dist/uploads/generateFileData.js +29 -16
  314. package/dist/uploads/generateFileData.js.map +1 -1
  315. package/dist/uploads/getBaseFields.d.ts.map +1 -1
  316. package/dist/uploads/getBaseFields.js +3 -1
  317. package/dist/uploads/getBaseFields.js.map +1 -1
  318. package/dist/uploads/getExternalFile.d.ts +1 -1
  319. package/dist/uploads/getExternalFile.d.ts.map +1 -1
  320. package/dist/uploads/getExternalFile.js +60 -15
  321. package/dist/uploads/getExternalFile.js.map +1 -1
  322. package/dist/uploads/getFileTypeFallback.d.ts.map +1 -1
  323. package/dist/uploads/getFileTypeFallback.js +3 -1
  324. package/dist/uploads/getFileTypeFallback.js.map +1 -1
  325. package/dist/uploads/getFileTypeIdentity.d.ts +19 -0
  326. package/dist/uploads/getFileTypeIdentity.d.ts.map +1 -0
  327. package/dist/uploads/getFileTypeIdentity.js +34 -0
  328. package/dist/uploads/getFileTypeIdentity.js.map +1 -0
  329. package/dist/uploads/getFileTypeIdentity.spec.js +43 -0
  330. package/dist/uploads/getFileTypeIdentity.spec.js.map +1 -0
  331. package/dist/uploads/isProcessableImage.d.ts +2 -0
  332. package/dist/uploads/isProcessableImage.d.ts.map +1 -0
  333. package/dist/uploads/isProcessableImage.js +7 -0
  334. package/dist/uploads/isProcessableImage.js.map +1 -0
  335. package/dist/uploads/safeFetch.d.ts.map +1 -1
  336. package/dist/uploads/safeFetch.js +3 -2
  337. package/dist/uploads/safeFetch.js.map +1 -1
  338. package/dist/uploads/sanitizeUploadData.d.ts +26 -0
  339. package/dist/uploads/sanitizeUploadData.d.ts.map +1 -0
  340. package/dist/uploads/sanitizeUploadData.js +159 -0
  341. package/dist/uploads/sanitizeUploadData.js.map +1 -0
  342. package/dist/uploads/sanitizeUploadData.spec.js +255 -0
  343. package/dist/uploads/sanitizeUploadData.spec.js.map +1 -0
  344. package/dist/uploads/types.d.ts +11 -3
  345. package/dist/uploads/types.d.ts.map +1 -1
  346. package/dist/uploads/types.js.map +1 -1
  347. package/dist/uploads/uploadContentSecurityPolicy.d.ts +2 -0
  348. package/dist/uploads/uploadContentSecurityPolicy.d.ts.map +1 -0
  349. package/dist/uploads/uploadContentSecurityPolicy.js +3 -0
  350. package/dist/uploads/uploadContentSecurityPolicy.js.map +1 -0
  351. package/dist/uploads/validateISOBaseMediaFile.d.ts +4 -0
  352. package/dist/uploads/validateISOBaseMediaFile.d.ts.map +1 -0
  353. package/dist/uploads/validateISOBaseMediaFile.js +91 -0
  354. package/dist/uploads/validateISOBaseMediaFile.js.map +1 -0
  355. package/dist/uploads/validateISOBaseMediaFile.spec.js +130 -0
  356. package/dist/uploads/validateISOBaseMediaFile.spec.js.map +1 -0
  357. package/dist/uploads/validateSvg.d.ts +10 -4
  358. package/dist/uploads/validateSvg.d.ts.map +1 -1
  359. package/dist/uploads/validateSvg.js +280 -40
  360. package/dist/uploads/validateSvg.js.map +1 -1
  361. package/dist/uploads/validateSvg.spec.js +123 -0
  362. package/dist/uploads/validateSvg.spec.js.map +1 -0
  363. package/dist/uploads/validateUploadFilename.d.ts +3 -0
  364. package/dist/uploads/validateUploadFilename.d.ts.map +1 -0
  365. package/dist/uploads/validateUploadFilename.js +13 -0
  366. package/dist/uploads/validateUploadFilename.js.map +1 -0
  367. package/dist/uploads/validateUploadFilename.spec.js +24 -0
  368. package/dist/uploads/validateUploadFilename.spec.js.map +1 -0
  369. package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
  370. package/dist/utilities/addDataAndFileToRequest.js +28 -1
  371. package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
  372. package/dist/utilities/addDataAndFileToRequest.spec.js +158 -0
  373. package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
  374. package/dist/utilities/createPayloadRequest.d.ts.map +1 -1
  375. package/dist/utilities/createPayloadRequest.js +2 -1
  376. package/dist/utilities/createPayloadRequest.js.map +1 -1
  377. package/dist/utilities/deepCopyObject.d.ts.map +1 -1
  378. package/dist/utilities/deepCopyObject.js +110 -19
  379. package/dist/utilities/deepCopyObject.js.map +1 -1
  380. package/dist/utilities/deepCopyObject.spec.js +216 -0
  381. package/dist/utilities/deepCopyObject.spec.js.map +1 -0
  382. package/dist/utilities/fieldPath.d.ts +13 -0
  383. package/dist/utilities/fieldPath.d.ts.map +1 -0
  384. package/dist/utilities/fieldPath.js +51 -0
  385. package/dist/utilities/fieldPath.js.map +1 -0
  386. package/dist/utilities/fieldPath.spec.js +155 -0
  387. package/dist/utilities/fieldPath.spec.js.map +1 -0
  388. package/dist/utilities/getEntityPermissions/getEntityPermissions.d.ts.map +1 -1
  389. package/dist/utilities/getEntityPermissions/getEntityPermissions.js +2 -0
  390. package/dist/utilities/getEntityPermissions/getEntityPermissions.js.map +1 -1
  391. package/dist/utilities/getObjectDotNotation.d.ts.map +1 -1
  392. package/dist/utilities/getObjectDotNotation.js +7 -1
  393. package/dist/utilities/getObjectDotNotation.js.map +1 -1
  394. package/dist/utilities/getObjectDotNotation.spec.js +33 -0
  395. package/dist/utilities/getObjectDotNotation.spec.js.map +1 -0
  396. package/dist/utilities/getSafeRedirect.spec.js +14 -0
  397. package/dist/utilities/getSafeRedirect.spec.js.map +1 -1
  398. package/dist/utilities/getTopLevelFieldNames.d.ts +2 -0
  399. package/dist/utilities/getTopLevelFieldNames.d.ts.map +1 -0
  400. package/dist/utilities/getTopLevelFieldNames.js +3 -0
  401. package/dist/utilities/getTopLevelFieldNames.js.map +1 -0
  402. package/dist/utilities/getTopLevelFieldNames.spec.js +18 -0
  403. package/dist/utilities/getTopLevelFieldNames.spec.js.map +1 -0
  404. package/dist/utilities/handleEndpoints.js +1 -1
  405. package/dist/utilities/handleEndpoints.js.map +1 -1
  406. package/dist/utilities/parseParams/index.d.ts.map +1 -1
  407. package/dist/utilities/parseParams/index.js +3 -1
  408. package/dist/utilities/parseParams/index.js.map +1 -1
  409. package/dist/utilities/parseParams/index.spec.js +8 -0
  410. package/dist/utilities/parseParams/index.spec.js.map +1 -1
  411. package/dist/utilities/telemetry/featureInfo/getFeatureInfo.d.ts +4 -0
  412. package/dist/utilities/telemetry/featureInfo/getFeatureInfo.d.ts.map +1 -0
  413. package/dist/utilities/telemetry/featureInfo/getFeatureInfo.js +248 -0
  414. package/dist/utilities/telemetry/featureInfo/getFeatureInfo.js.map +1 -0
  415. package/dist/utilities/telemetry/featureInfo/types.d.ts +62 -0
  416. package/dist/utilities/telemetry/featureInfo/types.d.ts.map +1 -0
  417. package/dist/utilities/telemetry/featureInfo/types.js +3 -0
  418. package/dist/utilities/telemetry/featureInfo/types.js.map +1 -0
  419. package/dist/utilities/telemetry/getProjectContext.d.ts +14 -0
  420. package/dist/utilities/telemetry/getProjectContext.d.ts.map +1 -0
  421. package/dist/utilities/telemetry/getProjectContext.js +36 -0
  422. package/dist/utilities/telemetry/getProjectContext.js.map +1 -0
  423. package/dist/utilities/telemetry/index.d.ts +7 -1
  424. package/dist/utilities/telemetry/index.d.ts.map +1 -1
  425. package/dist/utilities/telemetry/index.js +22 -0
  426. package/dist/utilities/telemetry/index.js.map +1 -1
  427. package/dist/utilities/traverseFields.d.ts.map +1 -1
  428. package/dist/utilities/traverseFields.js +5 -14
  429. package/dist/utilities/traverseFields.js.map +1 -1
  430. package/dist/utilities/unflatten.d.ts.map +1 -1
  431. package/dist/utilities/unflatten.js +21 -9
  432. package/dist/utilities/unflatten.js.map +1 -1
  433. package/dist/utilities/unflatten.spec.js +38 -0
  434. package/dist/utilities/unflatten.spec.js.map +1 -0
  435. package/dist/versions/allLocalesPublicationStatus.d.ts +38 -0
  436. package/dist/versions/allLocalesPublicationStatus.d.ts.map +1 -0
  437. package/dist/versions/allLocalesPublicationStatus.js +57 -0
  438. package/dist/versions/allLocalesPublicationStatus.js.map +1 -0
  439. package/dist/versions/getLatestGlobalVersion.d.ts.map +1 -1
  440. package/dist/versions/getLatestGlobalVersion.js +10 -2
  441. package/dist/versions/getLatestGlobalVersion.js.map +1 -1
  442. package/dist/versions/getRestoredStatusesToAuthorize.d.ts +9 -0
  443. package/dist/versions/getRestoredStatusesToAuthorize.d.ts.map +1 -0
  444. package/dist/versions/getRestoredStatusesToAuthorize.js +22 -0
  445. package/dist/versions/getRestoredStatusesToAuthorize.js.map +1 -0
  446. package/dist/versions/getRestoredStatusesToAuthorize.spec.js +50 -0
  447. package/dist/versions/getRestoredStatusesToAuthorize.spec.js.map +1 -0
  448. package/dist/versions/schedule/job.d.ts +2 -2
  449. package/dist/versions/schedule/job.d.ts.map +1 -1
  450. package/dist/versions/schedule/job.js +11 -7
  451. package/dist/versions/schedule/job.js.map +1 -1
  452. package/dist/versions/schedule/types.d.ts +5 -1
  453. package/dist/versions/schedule/types.d.ts.map +1 -1
  454. package/dist/versions/schedule/types.js.map +1 -1
  455. package/package.json +4 -2
  456. package/dist/auth/baseFields/apiKey.d.ts +0 -3
  457. package/dist/auth/baseFields/apiKey.d.ts.map +0 -1
  458. package/dist/auth/baseFields/apiKey.js +0 -59
  459. package/dist/auth/baseFields/apiKey.js.map +0 -1
@@ -1,6 +1,20 @@
1
1
  import { jwtVerify } from 'jose';
2
2
  import { extractJWT } from '../extractJWT.js';
3
- async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
3
+ import { JWT_AUTH_VERSION } from '../jwtAuth.js';
4
+ const getJWTAuthenticationReference = (token)=>{
5
+ const { id, collection, sid } = token;
6
+ if (typeof collection !== 'string' || typeof id !== 'number' && typeof id !== 'string' || typeof sid !== 'undefined' && typeof sid !== 'string') {
7
+ return null;
8
+ }
9
+ return {
10
+ id,
11
+ collection,
12
+ ...sid ? {
13
+ sid
14
+ } : {}
15
+ };
16
+ };
17
+ async function autoLogin({ fallbackLocale, isGraphQL, locale, payload, req, strategyName = 'local-jwt' }) {
4
18
  if (typeof payload?.config?.admin?.autoLogin !== 'object' || payload.config.admin?.autoLogin.prefillOnly || !payload?.config?.admin?.autoLogin || !payload.config.admin?.autoLogin.email && !payload.config.admin?.autoLogin.username) {
5
19
  return {
6
20
  user: null
@@ -26,8 +40,11 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
26
40
  const user = (await payload.find({
27
41
  collection: collection.config.slug,
28
42
  depth: isGraphQL ? 0 : collection.config.auth.depth,
43
+ fallbackLocale,
29
44
  limit: 1,
45
+ locale,
30
46
  pagination: false,
47
+ req,
31
48
  where
32
49
  })).docs[0];
33
50
  if (!user) {
@@ -43,7 +60,10 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
43
60
  }
44
61
  /**
45
62
  * Authentication strategy function for JWT tokens
46
- */ export const JWTAuthentication = async ({ headers, isGraphQL = false, payload, strategyName = 'local-jwt' })=>{
63
+ */ export const JWTAuthentication = async ({ headers, isGraphQL = false, payload, req, strategyName = 'local-jwt' })=>{
64
+ const requestFallbackLocale = req?.fallbackLocale;
65
+ const requestLocale = req?.locale;
66
+ const requestDepth = req?.query?.depth;
47
67
  try {
48
68
  const token = extractJWT({
49
69
  headers,
@@ -52,8 +72,11 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
52
72
  if (!token) {
53
73
  if (headers.get('DisableAutologin') !== 'true') {
54
74
  return await autoLogin({
75
+ fallbackLocale: requestFallbackLocale,
55
76
  isGraphQL,
77
+ locale: requestLocale,
56
78
  payload,
79
+ req,
57
80
  strategyName
58
81
  });
59
82
  }
@@ -62,22 +85,36 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
62
85
  };
63
86
  }
64
87
  const secretKey = new TextEncoder().encode(payload.secret);
65
- const { payload: decodedPayload } = await jwtVerify(token, secretKey);
66
- const collection = payload.collections[decodedPayload.collection];
88
+ const { payload: decodedPayload, protectedHeader } = await jwtVerify(token, secretKey);
89
+ if (protectedHeader.authVersion !== JWT_AUTH_VERSION) {
90
+ throw new Error('Invalid JWT protected header');
91
+ }
92
+ const authenticationReference = getJWTAuthenticationReference(decodedPayload);
93
+ if (!authenticationReference) {
94
+ throw new Error('Invalid JWT authentication reference');
95
+ }
96
+ const collection = Object.prototype.hasOwnProperty.call(payload.collections, authenticationReference.collection) ? payload.collections[authenticationReference.collection] : undefined;
97
+ if (!collection?.config.auth || collection.config.auth.disableLocalStrategy) {
98
+ throw new Error('JWT authentication requires an auth-enabled local collection');
99
+ }
67
100
  const user = await payload.findByID({
68
- id: decodedPayload.id,
69
- collection: decodedPayload.collection,
70
- depth: isGraphQL ? 0 : collection.config.auth.depth
101
+ id: authenticationReference.id,
102
+ collection: authenticationReference.collection,
103
+ depth: isGraphQL ? 0 : collection.config.auth.depth,
104
+ fallbackLocale: requestFallbackLocale,
105
+ locale: requestLocale,
106
+ overrideAccess: true,
107
+ req
71
108
  });
72
109
  if (user && (!collection.config.auth.verify || user._verified)) {
73
110
  if (collection.config.auth.useSessions) {
74
- const existingSession = (user.sessions || []).find(({ id })=>id === decodedPayload.sid);
75
- if (!existingSession || !decodedPayload.sid) {
111
+ const existingSession = (user.sessions || []).find(({ id })=>id === authenticationReference.sid);
112
+ if (!existingSession || !authenticationReference.sid) {
76
113
  return {
77
114
  user: null
78
115
  };
79
116
  }
80
- user._sid = decodedPayload.sid;
117
+ user._sid = authenticationReference.sid;
81
118
  }
82
119
  user.collection = collection.config.slug;
83
120
  user._strategy = strategyName;
@@ -87,8 +124,11 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
87
124
  } else {
88
125
  if (headers.get('DisableAutologin') !== 'true') {
89
126
  return await autoLogin({
127
+ fallbackLocale: requestFallbackLocale,
90
128
  isGraphQL,
129
+ locale: requestLocale,
91
130
  payload,
131
+ req,
92
132
  strategyName
93
133
  });
94
134
  }
@@ -99,14 +139,25 @@ async function autoLogin({ isGraphQL, payload, strategyName = 'local-jwt' }) {
99
139
  } catch (ignore) {
100
140
  if (headers.get('DisableAutologin') !== 'true') {
101
141
  return await autoLogin({
142
+ fallbackLocale: requestFallbackLocale,
102
143
  isGraphQL,
144
+ locale: requestLocale,
103
145
  payload,
146
+ req,
104
147
  strategyName
105
148
  });
106
149
  }
107
150
  return {
108
151
  user: null
109
152
  };
153
+ } finally{
154
+ if (req) {
155
+ req.fallbackLocale = requestFallbackLocale;
156
+ req.locale = requestLocale;
157
+ if (req.query) {
158
+ req.query.depth = requestDepth;
159
+ }
160
+ }
110
161
  }
111
162
  };
112
163
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/auth/strategies/jwt.ts"],"sourcesContent":["import { jwtVerify } from 'jose'\n\nimport type { Payload, Where } from '../../types/index.js'\nimport type { AuthStrategyFunction, AuthStrategyResult } from '../index.js'\n\nimport { extractJWT } from '../extractJWT.js'\n\ntype JWTToken = {\n collection: string\n id: string\n sid?: string\n}\n\nasync function autoLogin({\n isGraphQL,\n payload,\n strategyName = 'local-jwt',\n}: {\n isGraphQL: boolean\n payload: Payload\n strategyName?: string\n}): Promise<{\n user: AuthStrategyResult['user']\n}> {\n if (\n typeof payload?.config?.admin?.autoLogin !== 'object' ||\n payload.config.admin?.autoLogin.prefillOnly ||\n !payload?.config?.admin?.autoLogin ||\n (!payload.config.admin?.autoLogin.email && !payload.config.admin?.autoLogin.username)\n ) {\n return { user: null }\n }\n\n const collection = payload.collections[payload.config.admin.user]\n\n const where: Where = {\n or: [],\n }\n if (payload.config.admin?.autoLogin.email) {\n where.or?.push({\n email: {\n equals: payload.config.admin?.autoLogin.email,\n },\n })\n } else if (payload.config.admin?.autoLogin.username) {\n where.or?.push({\n username: {\n equals: payload.config.admin?.autoLogin.username,\n },\n })\n }\n\n const user = (\n await payload.find({\n collection: collection!.config.slug,\n depth: isGraphQL ? 0 : collection!.config.auth.depth,\n limit: 1,\n pagination: false,\n where,\n })\n ).docs[0] as AuthStrategyResult['user']\n\n if (!user) {\n return { user: null }\n }\n user.collection = collection!.config.slug\n user._strategy = strategyName\n\n return {\n user,\n }\n}\n\n/**\n * Authentication strategy function for JWT tokens\n */\nexport const JWTAuthentication: AuthStrategyFunction = async ({\n headers,\n isGraphQL = false,\n payload,\n strategyName = 'local-jwt',\n}) => {\n try {\n const token = extractJWT({ headers, payload })\n\n if (!token) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({ isGraphQL, payload, strategyName })\n }\n return { user: null }\n }\n\n const secretKey = new TextEncoder().encode(payload.secret)\n const { payload: decodedPayload } = await jwtVerify<JWTToken>(token, secretKey)\n const collection = payload.collections[decodedPayload.collection]\n\n const user = (await payload.findByID({\n id: decodedPayload.id,\n collection: decodedPayload.collection,\n depth: isGraphQL ? 0 : collection!.config.auth.depth,\n })) as AuthStrategyResult['user']\n\n if (user && (!collection!.config.auth.verify || user._verified)) {\n if (collection!.config.auth.useSessions) {\n const existingSession = (user.sessions || []).find(({ id }) => id === decodedPayload.sid)\n\n if (!existingSession || !decodedPayload.sid) {\n return {\n user: null,\n }\n }\n\n user._sid = decodedPayload.sid\n }\n\n user.collection = collection!.config.slug\n user._strategy = strategyName\n return {\n user,\n }\n } else {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({ isGraphQL, payload, strategyName })\n }\n return { user: null }\n }\n } catch (ignore) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({ isGraphQL, payload, strategyName })\n }\n return { user: null }\n }\n}\n"],"names":["jwtVerify","extractJWT","autoLogin","isGraphQL","payload","strategyName","config","admin","prefillOnly","email","username","user","collection","collections","where","or","push","equals","find","slug","depth","auth","limit","pagination","docs","_strategy","JWTAuthentication","headers","token","get","secretKey","TextEncoder","encode","secret","decodedPayload","findByID","id","verify","_verified","useSessions","existingSession","sessions","sid","_sid","ignore"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/auth/strategies/jwt.ts"],"sourcesContent":["import { jwtVerify } from 'jose'\n\nimport type { Payload, PayloadRequest, Where } from '../../types/index.js'\nimport type { AuthStrategyFunction, AuthStrategyResult } from '../index.js'\n\nimport { extractJWT } from '../extractJWT.js'\nimport { JWT_AUTH_VERSION } from '../jwtAuth.js'\n\ntype DecodedJWTPayload = Record<string, unknown>\n\ntype JWTAuthenticationReference = {\n collection: string\n id: number | string\n sid?: string\n}\n\nconst getJWTAuthenticationReference = (\n token: DecodedJWTPayload,\n): JWTAuthenticationReference | null => {\n const { id, collection, sid } = token\n\n if (\n typeof collection !== 'string' ||\n (typeof id !== 'number' && typeof id !== 'string') ||\n (typeof sid !== 'undefined' && typeof sid !== 'string')\n ) {\n return null\n }\n\n return {\n id,\n collection,\n ...(sid ? { sid } : {}),\n }\n}\n\nasync function autoLogin({\n fallbackLocale,\n isGraphQL,\n locale,\n payload,\n req,\n strategyName = 'local-jwt',\n}: {\n fallbackLocale?: PayloadRequest['fallbackLocale']\n isGraphQL: boolean\n locale?: PayloadRequest['locale']\n payload: Payload\n req?: PayloadRequest\n strategyName?: string\n}): Promise<{\n user: AuthStrategyResult['user']\n}> {\n if (\n typeof payload?.config?.admin?.autoLogin !== 'object' ||\n payload.config.admin?.autoLogin.prefillOnly ||\n !payload?.config?.admin?.autoLogin ||\n (!payload.config.admin?.autoLogin.email && !payload.config.admin?.autoLogin.username)\n ) {\n return { user: null }\n }\n\n const collection = payload.collections[payload.config.admin.user]\n\n const where: Where = {\n or: [],\n }\n if (payload.config.admin?.autoLogin.email) {\n where.or?.push({\n email: {\n equals: payload.config.admin?.autoLogin.email,\n },\n })\n } else if (payload.config.admin?.autoLogin.username) {\n where.or?.push({\n username: {\n equals: payload.config.admin?.autoLogin.username,\n },\n })\n }\n\n const user = (\n await payload.find({\n collection: collection!.config.slug,\n depth: isGraphQL ? 0 : collection!.config.auth.depth,\n fallbackLocale,\n limit: 1,\n locale,\n pagination: false,\n req,\n where,\n })\n ).docs[0] as AuthStrategyResult['user']\n\n if (!user) {\n return { user: null }\n }\n user.collection = collection!.config.slug\n user._strategy = strategyName\n\n return {\n user,\n }\n}\n\n/**\n * Authentication strategy function for JWT tokens\n */\nexport const JWTAuthentication: AuthStrategyFunction = async ({\n headers,\n isGraphQL = false,\n payload,\n req,\n strategyName = 'local-jwt',\n}) => {\n const requestFallbackLocale = req?.fallbackLocale\n const requestLocale = req?.locale\n const requestDepth = req?.query?.depth\n\n try {\n const token = extractJWT({ headers, payload })\n\n if (!token) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({\n fallbackLocale: requestFallbackLocale,\n isGraphQL,\n locale: requestLocale,\n payload,\n req,\n strategyName,\n })\n }\n return { user: null }\n }\n\n const secretKey = new TextEncoder().encode(payload.secret)\n const { payload: decodedPayload, protectedHeader } = await jwtVerify<DecodedJWTPayload>(\n token,\n secretKey,\n )\n\n if (protectedHeader.authVersion !== JWT_AUTH_VERSION) {\n throw new Error('Invalid JWT protected header')\n }\n\n const authenticationReference = getJWTAuthenticationReference(decodedPayload)\n\n if (!authenticationReference) {\n throw new Error('Invalid JWT authentication reference')\n }\n\n const collection = Object.prototype.hasOwnProperty.call(\n payload.collections,\n authenticationReference.collection,\n )\n ? payload.collections[authenticationReference.collection]\n : undefined\n\n if (!collection?.config.auth || collection.config.auth.disableLocalStrategy) {\n throw new Error('JWT authentication requires an auth-enabled local collection')\n }\n\n const user = (await payload.findByID({\n id: authenticationReference.id,\n collection: authenticationReference.collection,\n depth: isGraphQL ? 0 : collection.config.auth.depth,\n fallbackLocale: requestFallbackLocale,\n locale: requestLocale,\n overrideAccess: true,\n req,\n })) as AuthStrategyResult['user']\n\n if (user && (!collection.config.auth.verify || user._verified)) {\n if (collection.config.auth.useSessions) {\n const existingSession = (user.sessions || []).find(\n ({ id }) => id === authenticationReference.sid,\n )\n\n if (!existingSession || !authenticationReference.sid) {\n return {\n user: null,\n }\n }\n\n user._sid = authenticationReference.sid\n }\n\n user.collection = collection.config.slug\n user._strategy = strategyName\n return {\n user,\n }\n } else {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({\n fallbackLocale: requestFallbackLocale,\n isGraphQL,\n locale: requestLocale,\n payload,\n req,\n strategyName,\n })\n }\n return { user: null }\n }\n } catch (ignore) {\n if (headers.get('DisableAutologin') !== 'true') {\n return await autoLogin({\n fallbackLocale: requestFallbackLocale,\n isGraphQL,\n locale: requestLocale,\n payload,\n req,\n strategyName,\n })\n }\n return { user: null }\n } finally {\n if (req) {\n req.fallbackLocale = requestFallbackLocale!\n req.locale = requestLocale!\n\n if (req.query) {\n req.query.depth = requestDepth\n }\n }\n }\n}\n"],"names":["jwtVerify","extractJWT","JWT_AUTH_VERSION","getJWTAuthenticationReference","token","id","collection","sid","autoLogin","fallbackLocale","isGraphQL","locale","payload","req","strategyName","config","admin","prefillOnly","email","username","user","collections","where","or","push","equals","find","slug","depth","auth","limit","pagination","docs","_strategy","JWTAuthentication","headers","requestFallbackLocale","requestLocale","requestDepth","query","get","secretKey","TextEncoder","encode","secret","decodedPayload","protectedHeader","authVersion","Error","authenticationReference","Object","prototype","hasOwnProperty","call","undefined","disableLocalStrategy","findByID","overrideAccess","verify","_verified","useSessions","existingSession","sessions","_sid","ignore"],"mappings":"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"}
@@ -0,0 +1,281 @@
1
+ import { SignJWT } from 'jose';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { getFieldsToSign } from '../getFieldsToSign.js';
4
+ import { jwtSign } from '../jwt.js';
5
+ import { JWT_AUTH_VERSION } from '../jwtAuth.js';
6
+ import { JWTAuthentication } from './jwt.js';
7
+ const secret = '01234567890123456789012345678901';
8
+ const signToken = async (tokenData, protectedHeader = {
9
+ authVersion: JWT_AUTH_VERSION
10
+ })=>new SignJWT(tokenData).setProtectedHeader({
11
+ alg: 'HS256',
12
+ typ: 'JWT',
13
+ ...protectedHeader
14
+ }).sign(new TextEncoder().encode(secret));
15
+ const createPayload = ()=>({
16
+ collections: {
17
+ admins: {
18
+ config: {
19
+ auth: {
20
+ depth: 0,
21
+ useSessions: false,
22
+ verify: false
23
+ },
24
+ slug: 'admins'
25
+ }
26
+ },
27
+ disabledUsers: {
28
+ config: {
29
+ auth: {
30
+ depth: 0,
31
+ disableLocalStrategy: true,
32
+ useSessions: false,
33
+ verify: false
34
+ },
35
+ slug: 'disabledUsers'
36
+ }
37
+ },
38
+ posts: {
39
+ config: {
40
+ auth: false,
41
+ slug: 'posts'
42
+ }
43
+ },
44
+ users: {
45
+ config: {
46
+ auth: {
47
+ depth: 0,
48
+ useSessions: false,
49
+ verify: false
50
+ },
51
+ slug: 'users'
52
+ }
53
+ }
54
+ },
55
+ config: {
56
+ admin: {
57
+ autoLogin: {
58
+ email: 'dev@example.com'
59
+ },
60
+ user: 'users'
61
+ },
62
+ auth: {
63
+ jwtOrder: [
64
+ 'Bearer'
65
+ ]
66
+ },
67
+ cookiePrefix: 'payload',
68
+ csrf: []
69
+ },
70
+ find: vi.fn(async ({ depth, req })=>{
71
+ if (req?.query) {
72
+ req.query.depth = depth;
73
+ }
74
+ return {
75
+ docs: [
76
+ {
77
+ id: 'auto-login-user'
78
+ }
79
+ ]
80
+ };
81
+ }),
82
+ findByID: async ({ collection, id })=>({
83
+ id,
84
+ role: collection === 'posts' ? 'admin' : 'user'
85
+ }),
86
+ secret
87
+ });
88
+ const authenticate = async (token, payload = createPayload())=>{
89
+ return JWTAuthentication({
90
+ headers: new Headers({
91
+ Authorization: `Bearer ${token}`,
92
+ DisableAutologin: 'true'
93
+ }),
94
+ payload
95
+ });
96
+ };
97
+ describe('JWTAuthentication', ()=>{
98
+ it.each([
99
+ {
100
+ expectedDepth: 2,
101
+ isGraphQL: false,
102
+ label: 'REST'
103
+ },
104
+ {
105
+ expectedDepth: 0,
106
+ isGraphQL: true,
107
+ label: 'GraphQL'
108
+ }
109
+ ])('should use the expected auth depth for $label auto-login', async ({ expectedDepth, isGraphQL })=>{
110
+ const payload = createPayload();
111
+ const req = {
112
+ fallbackLocale: false,
113
+ locale: 'en',
114
+ query: {
115
+ depth: 9
116
+ }
117
+ };
118
+ payload.collections.users.config.auth.depth = 2;
119
+ const result = await JWTAuthentication({
120
+ headers: new Headers(),
121
+ isGraphQL,
122
+ payload,
123
+ req
124
+ });
125
+ expect(payload.find).toHaveBeenCalledWith(expect.objectContaining({
126
+ depth: expectedDepth,
127
+ fallbackLocale: false,
128
+ locale: 'en',
129
+ req
130
+ }));
131
+ expect(req.query.depth).toBe(9);
132
+ expect(result.user).toMatchObject({
133
+ _strategy: 'local-jwt',
134
+ collection: 'users',
135
+ id: 'auto-login-user'
136
+ });
137
+ });
138
+ it('should authenticate flat v3 token data with a protected-header version marker', async ()=>{
139
+ const { token } = await jwtSign({
140
+ fieldsToSign: {
141
+ collection: 'users',
142
+ id: 'user-id',
143
+ role: 'user'
144
+ },
145
+ secret,
146
+ tokenExpiration: 3600
147
+ });
148
+ await expect(authenticate(token)).resolves.toMatchObject({
149
+ user: {
150
+ _strategy: 'local-jwt',
151
+ collection: 'users',
152
+ id: 'user-id'
153
+ }
154
+ });
155
+ });
156
+ it('should reject existing tokens without a protected-header version marker', async ()=>{
157
+ await expect(authenticate(await signToken({
158
+ collection: 'users',
159
+ id: 'user-id',
160
+ role: 'user'
161
+ }, {}))).resolves.toEqual({
162
+ user: null
163
+ });
164
+ });
165
+ it('should reject tokens with a payload version value but no protected-header marker', async ()=>{
166
+ await expect(authenticate(await signToken({
167
+ collection: 'users',
168
+ id: 'user-id',
169
+ authVersion: JWT_AUTH_VERSION,
170
+ role: 'user'
171
+ }, {}))).resolves.toEqual({
172
+ user: null
173
+ });
174
+ });
175
+ it('should reject tokens with an invalid protected-header version', async ()=>{
176
+ await expect(authenticate(await signToken({
177
+ collection: 'users',
178
+ id: 'user-id'
179
+ }, {
180
+ authVersion: JWT_AUTH_VERSION + 1
181
+ }))).resolves.toEqual({
182
+ user: null
183
+ });
184
+ await expect(authenticate(await signToken({
185
+ collection: 'users',
186
+ id: 'user-id'
187
+ }, {
188
+ authVersion: '1'
189
+ }))).resolves.toEqual({
190
+ user: null
191
+ });
192
+ });
193
+ it('should reject inherited collection names', async ()=>{
194
+ const payload = createPayload();
195
+ Object.setPrototypeOf(payload.collections, {
196
+ toString: payload.collections.users
197
+ });
198
+ await expect(authenticate(await signToken({
199
+ collection: 'toString',
200
+ id: 'user-id'
201
+ }), payload)).resolves.toEqual({
202
+ user: null
203
+ });
204
+ });
205
+ it('should accept numeric identifier zero', async ()=>{
206
+ await expect(authenticate(await signToken({
207
+ collection: 'users',
208
+ id: 0
209
+ }))).resolves.toMatchObject({
210
+ user: {
211
+ id: 0
212
+ }
213
+ });
214
+ });
215
+ it('should reject tokens targeting a non-auth collection', async ()=>{
216
+ await expect(authenticate(await signToken({
217
+ collection: 'posts',
218
+ id: 'post-id'
219
+ }))).resolves.toEqual({
220
+ user: null
221
+ });
222
+ });
223
+ it('should reject tokens targeting a collection with local auth disabled', async ()=>{
224
+ await expect(authenticate(await signToken({
225
+ collection: 'disabledUsers',
226
+ id: 'user-id'
227
+ }))).resolves.toEqual({
228
+ user: null
229
+ });
230
+ });
231
+ it('should reject malformed identity fields', async ()=>{
232
+ await expect(authenticate(await signToken({
233
+ collection: 'users',
234
+ id: {
235
+ value: 'user-id'
236
+ }
237
+ }))).resolves.toEqual({
238
+ user: null
239
+ });
240
+ });
241
+ it('should ignore saveToJWT aliases that target Payload-owned fields end to end', async ()=>{
242
+ const collectionConfig = {
243
+ fields: [
244
+ {
245
+ name: 'impersonatedID',
246
+ saveToJWT: 'id',
247
+ type: 'text'
248
+ },
249
+ {
250
+ name: 'impersonatedCollection',
251
+ saveToJWT: 'collection',
252
+ type: 'text'
253
+ }
254
+ ],
255
+ slug: 'users'
256
+ };
257
+ const fieldsToSign = getFieldsToSign({
258
+ collectionConfig,
259
+ email: 'attacker@example.com',
260
+ user: {
261
+ id: 'attacker-id',
262
+ impersonatedCollection: 'admins',
263
+ impersonatedID: 'victim-id'
264
+ }
265
+ });
266
+ const { token } = await jwtSign({
267
+ fieldsToSign,
268
+ secret,
269
+ tokenExpiration: 3600
270
+ });
271
+ const result = await authenticate(token);
272
+ expect(result.user).toMatchObject({
273
+ collection: 'users',
274
+ id: 'attacker-id'
275
+ });
276
+ expect(result.user?.collection).not.toBe('admins');
277
+ expect(result.user?.id).not.toBe('victim-id');
278
+ });
279
+ });
280
+
281
+ //# sourceMappingURL=jwt.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/auth/strategies/jwt.spec.ts"],"sourcesContent":["import type { CollectionConfig } from '../../collections/config/types.js'\nimport type { Payload, PayloadRequest } from '../../types/index.js'\n\nimport { SignJWT } from 'jose'\nimport { describe, expect, it, vi } from 'vitest'\n\nimport { getFieldsToSign } from '../getFieldsToSign.js'\nimport { jwtSign } from '../jwt.js'\nimport { JWT_AUTH_VERSION } from '../jwtAuth.js'\nimport { JWTAuthentication } from './jwt.js'\n\nconst secret = '01234567890123456789012345678901'\n\nconst signToken = async (\n tokenData: Record<string, unknown>,\n protectedHeader: { authVersion?: number | string } = {\n authVersion: JWT_AUTH_VERSION,\n },\n): Promise<string> =>\n new SignJWT(tokenData)\n .setProtectedHeader({ alg: 'HS256', typ: 'JWT', ...protectedHeader })\n .sign(new TextEncoder().encode(secret))\n\nconst createPayload = (): Payload =>\n ({\n collections: {\n admins: {\n config: {\n auth: {\n depth: 0,\n useSessions: false,\n verify: false,\n },\n slug: 'admins',\n },\n },\n disabledUsers: {\n config: {\n auth: {\n depth: 0,\n disableLocalStrategy: true,\n useSessions: false,\n verify: false,\n },\n slug: 'disabledUsers',\n },\n },\n posts: {\n config: {\n auth: false,\n slug: 'posts',\n },\n },\n users: {\n config: {\n auth: {\n depth: 0,\n useSessions: false,\n verify: false,\n },\n slug: 'users',\n },\n },\n },\n config: {\n admin: {\n autoLogin: {\n email: 'dev@example.com',\n },\n user: 'users',\n },\n auth: {\n jwtOrder: ['Bearer'],\n },\n cookiePrefix: 'payload',\n csrf: [],\n },\n find: vi.fn(async ({ depth, req }: { depth: number; req?: PayloadRequest }) => {\n if (req?.query) {\n req.query.depth = depth\n }\n\n return {\n docs: [{ id: 'auto-login-user' }],\n }\n }),\n findByID: async ({ collection, id }) => ({\n id,\n role: collection === 'posts' ? 'admin' : 'user',\n }),\n secret,\n }) as unknown as Payload\n\nconst authenticate = async (token: string, payload = createPayload()) => {\n return JWTAuthentication({\n headers: new Headers({\n Authorization: `Bearer ${token}`,\n DisableAutologin: 'true',\n }),\n payload,\n })\n}\n\ndescribe('JWTAuthentication', () => {\n it.each([\n { expectedDepth: 2, isGraphQL: false, label: 'REST' },\n { expectedDepth: 0, isGraphQL: true, label: 'GraphQL' },\n ])(\n 'should use the expected auth depth for $label auto-login',\n async ({ expectedDepth, isGraphQL }) => {\n const payload = createPayload()\n const req = {\n fallbackLocale: false,\n locale: 'en',\n query: { depth: 9 },\n } as PayloadRequest\n payload.collections.users.config.auth.depth = 2\n\n const result = await JWTAuthentication({\n headers: new Headers(),\n isGraphQL,\n payload,\n req,\n })\n\n expect(payload.find).toHaveBeenCalledWith(\n expect.objectContaining({\n depth: expectedDepth,\n fallbackLocale: false,\n locale: 'en',\n req,\n }),\n )\n expect(req.query.depth).toBe(9)\n expect(result.user).toMatchObject({\n _strategy: 'local-jwt',\n collection: 'users',\n id: 'auto-login-user',\n })\n },\n )\n\n it('should authenticate flat v3 token data with a protected-header version marker', async () => {\n const { token } = await jwtSign({\n fieldsToSign: {\n collection: 'users',\n id: 'user-id',\n role: 'user',\n },\n secret,\n tokenExpiration: 3600,\n })\n\n await expect(authenticate(token)).resolves.toMatchObject({\n user: {\n _strategy: 'local-jwt',\n collection: 'users',\n id: 'user-id',\n },\n })\n })\n\n it('should reject existing tokens without a protected-header version marker', async () => {\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n role: 'user',\n },\n {},\n ),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject tokens with a payload version value but no protected-header marker', async () => {\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n authVersion: JWT_AUTH_VERSION,\n role: 'user',\n },\n {},\n ),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject tokens with an invalid protected-header version', async () => {\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n },\n { authVersion: JWT_AUTH_VERSION + 1 },\n ),\n ),\n ).resolves.toEqual({ user: null })\n\n await expect(\n authenticate(\n await signToken(\n {\n collection: 'users',\n id: 'user-id',\n },\n { authVersion: '1' },\n ),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject inherited collection names', async () => {\n const payload = createPayload()\n Object.setPrototypeOf(payload.collections, { toString: payload.collections.users })\n\n await expect(\n authenticate(\n await signToken({\n collection: 'toString',\n id: 'user-id',\n }),\n payload,\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should accept numeric identifier zero', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'users',\n id: 0,\n }),\n ),\n ).resolves.toMatchObject({ user: { id: 0 } })\n })\n\n it('should reject tokens targeting a non-auth collection', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'posts',\n id: 'post-id',\n }),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject tokens targeting a collection with local auth disabled', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'disabledUsers',\n id: 'user-id',\n }),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should reject malformed identity fields', async () => {\n await expect(\n authenticate(\n await signToken({\n collection: 'users',\n id: { value: 'user-id' },\n }),\n ),\n ).resolves.toEqual({ user: null })\n })\n\n it('should ignore saveToJWT aliases that target Payload-owned fields end to end', async () => {\n const collectionConfig = {\n fields: [\n {\n name: 'impersonatedID',\n saveToJWT: 'id',\n type: 'text',\n },\n {\n name: 'impersonatedCollection',\n saveToJWT: 'collection',\n type: 'text',\n },\n ],\n slug: 'users',\n } as CollectionConfig\n\n const fieldsToSign = getFieldsToSign({\n collectionConfig,\n email: 'attacker@example.com',\n user: {\n id: 'attacker-id',\n impersonatedCollection: 'admins',\n impersonatedID: 'victim-id',\n } as PayloadRequest['user'],\n })\n const { token } = await jwtSign({\n fieldsToSign,\n secret,\n tokenExpiration: 3600,\n })\n const result = await authenticate(token)\n\n expect(result.user).toMatchObject({\n collection: 'users',\n id: 'attacker-id',\n })\n expect(result.user?.collection).not.toBe('admins')\n expect(result.user?.id).not.toBe('victim-id')\n })\n})\n"],"names":["SignJWT","describe","expect","it","vi","getFieldsToSign","jwtSign","JWT_AUTH_VERSION","JWTAuthentication","secret","signToken","tokenData","protectedHeader","authVersion","setProtectedHeader","alg","typ","sign","TextEncoder","encode","createPayload","collections","admins","config","auth","depth","useSessions","verify","slug","disabledUsers","disableLocalStrategy","posts","users","admin","autoLogin","email","user","jwtOrder","cookiePrefix","csrf","find","fn","req","query","docs","id","findByID","collection","role","authenticate","token","payload","headers","Headers","Authorization","DisableAutologin","each","expectedDepth","isGraphQL","label","fallbackLocale","locale","result","toHaveBeenCalledWith","objectContaining","toBe","toMatchObject","_strategy","fieldsToSign","tokenExpiration","resolves","toEqual","Object","setPrototypeOf","toString","value","collectionConfig","fields","name","saveToJWT","type","impersonatedCollection","impersonatedID","not"],"mappings":"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"}
@@ -1,9 +1,13 @@
1
1
  import type { TypeWithID } from '../../../collections/config/types.js';
2
2
  type Doc = Record<string, unknown> & TypeWithID;
3
+ type AuthenticationResult = {
4
+ doc: Doc;
5
+ shouldUpdatePasswordHash: boolean;
6
+ };
3
7
  type Args = {
4
8
  doc: Doc;
5
9
  password: string;
6
10
  };
7
- export declare const authenticateLocalStrategy: ({ doc, password }: Args) => Promise<Doc | null>;
11
+ export declare const authenticateLocalStrategy: ({ doc, password, }: Args) => Promise<AuthenticationResult | null>;
8
12
  export {};
9
13
  //# sourceMappingURL=authenticate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../../../../src/auth/strategies/local/authenticate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAA;AAEtE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAAA;AAE/C,KAAK,IAAI,GAAG;IACV,GAAG,EAAE,GAAG,CAAA;IACR,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,yBAAyB,sBAA6B,IAAI,KAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CA+B3F,CAAA"}
1
+ {"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../../../../src/auth/strategies/local/authenticate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAA;AAItE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAAA;AAE/C,KAAK,oBAAoB,GAAG;IAC1B,GAAG,EAAE,GAAG,CAAA;IACR,wBAAwB,EAAE,OAAO,CAAA;CAClC,CAAA;AAED,KAAK,IAAI,GAAG;IACV,GAAG,EAAE,GAAG,CAAA;IACR,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,yBAAyB,uBAGnC,IAAI,KAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAmC5C,CAAA"}
@@ -1,17 +1,22 @@
1
1
  // @ts-strict-ignore
2
2
  import crypto from 'crypto';
3
+ import { getPasswordHashParameters, isCurrentPasswordHash } from './generatePasswordSaltHash.js';
3
4
  export const authenticateLocalStrategy = async ({ doc, password })=>{
4
5
  try {
5
6
  const { hash, salt } = doc;
6
7
  if (typeof salt === 'string' && typeof hash === 'string') {
8
+ const { hash: storedHash, iterations, keyLength } = getPasswordHashParameters(hash);
7
9
  const res = await new Promise((resolve, reject)=>{
8
- crypto.pbkdf2(password, salt, 25000, 512, 'sha256', (e, hashBuffer)=>{
10
+ crypto.pbkdf2(password, salt, iterations, keyLength, 'sha256', (e, hashBuffer)=>{
9
11
  if (e) {
10
12
  reject(e);
11
13
  }
12
- const storedHashBuffer = Buffer.from(hash, 'hex');
14
+ const storedHashBuffer = Buffer.from(storedHash, 'hex');
13
15
  if (hashBuffer.length === storedHashBuffer.length && crypto.timingSafeEqual(hashBuffer, storedHashBuffer)) {
14
- resolve(doc);
16
+ resolve({
17
+ doc,
18
+ shouldUpdatePasswordHash: !isCurrentPasswordHash(hash)
19
+ });
15
20
  } else {
16
21
  reject(new Error('Invalid password'));
17
22
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/auth/strategies/local/authenticate.ts"],"sourcesContent":["// @ts-strict-ignore\nimport crypto from 'crypto'\n\nimport type { TypeWithID } from '../../../collections/config/types.js'\n\ntype Doc = Record<string, unknown> & TypeWithID\n\ntype Args = {\n doc: Doc\n password: string\n}\n\nexport const authenticateLocalStrategy = async ({ doc, password }: Args): Promise<Doc | null> => {\n try {\n const { hash, salt } = doc\n\n if (typeof salt === 'string' && typeof hash === 'string') {\n const res = await new Promise<Doc | null>((resolve, reject) => {\n crypto.pbkdf2(password, salt, 25000, 512, 'sha256', (e, hashBuffer) => {\n if (e) {\n reject(e)\n }\n\n const storedHashBuffer = Buffer.from(hash, 'hex')\n\n if (\n hashBuffer.length === storedHashBuffer.length &&\n crypto.timingSafeEqual(hashBuffer, storedHashBuffer)\n ) {\n resolve(doc)\n } else {\n reject(new Error('Invalid password'))\n }\n })\n })\n\n return res\n }\n\n return null\n } catch (ignore) {\n return null\n }\n}\n"],"names":["crypto","authenticateLocalStrategy","doc","password","hash","salt","res","Promise","resolve","reject","pbkdf2","e","hashBuffer","storedHashBuffer","Buffer","from","length","timingSafeEqual","Error","ignore"],"mappings":"AAAA,oBAAoB;AACpB,OAAOA,YAAY,SAAQ;AAW3B,OAAO,MAAMC,4BAA4B,OAAO,EAAEC,GAAG,EAAEC,QAAQ,EAAQ;IACrE,IAAI;QACF,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGH;QAEvB,IAAI,OAAOG,SAAS,YAAY,OAAOD,SAAS,UAAU;YACxD,MAAME,MAAM,MAAM,IAAIC,QAAoB,CAACC,SAASC;gBAClDT,OAAOU,MAAM,CAACP,UAAUE,MAAM,OAAO,KAAK,UAAU,CAACM,GAAGC;oBACtD,IAAID,GAAG;wBACLF,OAAOE;oBACT;oBAEA,MAAME,mBAAmBC,OAAOC,IAAI,CAACX,MAAM;oBAE3C,IACEQ,WAAWI,MAAM,KAAKH,iBAAiBG,MAAM,IAC7ChB,OAAOiB,eAAe,CAACL,YAAYC,mBACnC;wBACAL,QAAQN;oBACV,OAAO;wBACLO,OAAO,IAAIS,MAAM;oBACnB;gBACF;YACF;YAEA,OAAOZ;QACT;QAEA,OAAO;IACT,EAAE,OAAOa,QAAQ;QACf,OAAO;IACT;AACF,EAAC"}
1
+ {"version":3,"sources":["../../../../src/auth/strategies/local/authenticate.ts"],"sourcesContent":["// @ts-strict-ignore\nimport crypto from 'crypto'\n\nimport type { TypeWithID } from '../../../collections/config/types.js'\n\nimport { getPasswordHashParameters, isCurrentPasswordHash } from './generatePasswordSaltHash.js'\n\ntype Doc = Record<string, unknown> & TypeWithID\n\ntype AuthenticationResult = {\n doc: Doc\n shouldUpdatePasswordHash: boolean\n}\n\ntype Args = {\n doc: Doc\n password: string\n}\n\nexport const authenticateLocalStrategy = async ({\n doc,\n password,\n}: Args): Promise<AuthenticationResult | null> => {\n try {\n const { hash, salt } = doc\n\n if (typeof salt === 'string' && typeof hash === 'string') {\n const { hash: storedHash, iterations, keyLength } = getPasswordHashParameters(hash)\n const res = await new Promise<AuthenticationResult | null>((resolve, reject) => {\n crypto.pbkdf2(password, salt, iterations, keyLength, 'sha256', (e, hashBuffer) => {\n if (e) {\n reject(e)\n }\n\n const storedHashBuffer = Buffer.from(storedHash, 'hex')\n\n if (\n hashBuffer.length === storedHashBuffer.length &&\n crypto.timingSafeEqual(hashBuffer, storedHashBuffer)\n ) {\n resolve({\n doc,\n shouldUpdatePasswordHash: !isCurrentPasswordHash(hash),\n })\n } else {\n reject(new Error('Invalid password'))\n }\n })\n })\n\n return res\n }\n\n return null\n } catch (ignore) {\n return null\n }\n}\n"],"names":["crypto","getPasswordHashParameters","isCurrentPasswordHash","authenticateLocalStrategy","doc","password","hash","salt","storedHash","iterations","keyLength","res","Promise","resolve","reject","pbkdf2","e","hashBuffer","storedHashBuffer","Buffer","from","length","timingSafeEqual","shouldUpdatePasswordHash","Error","ignore"],"mappings":"AAAA,oBAAoB;AACpB,OAAOA,YAAY,SAAQ;AAI3B,SAASC,yBAAyB,EAAEC,qBAAqB,QAAQ,gCAA+B;AAchG,OAAO,MAAMC,4BAA4B,OAAO,EAC9CC,GAAG,EACHC,QAAQ,EACH;IACL,IAAI;QACF,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGH;QAEvB,IAAI,OAAOG,SAAS,YAAY,OAAOD,SAAS,UAAU;YACxD,MAAM,EAAEA,MAAME,UAAU,EAAEC,UAAU,EAAEC,SAAS,EAAE,GAAGT,0BAA0BK;YAC9E,MAAMK,MAAM,MAAM,IAAIC,QAAqC,CAACC,SAASC;gBACnEd,OAAOe,MAAM,CAACV,UAAUE,MAAME,YAAYC,WAAW,UAAU,CAACM,GAAGC;oBACjE,IAAID,GAAG;wBACLF,OAAOE;oBACT;oBAEA,MAAME,mBAAmBC,OAAOC,IAAI,CAACZ,YAAY;oBAEjD,IACES,WAAWI,MAAM,KAAKH,iBAAiBG,MAAM,IAC7CrB,OAAOsB,eAAe,CAACL,YAAYC,mBACnC;wBACAL,QAAQ;4BACNT;4BACAmB,0BAA0B,CAACrB,sBAAsBI;wBACnD;oBACF,OAAO;wBACLQ,OAAO,IAAIU,MAAM;oBACnB;gBACF;YACF;YAEA,OAAOb;QACT;QAEA,OAAO;IACT,EAAE,OAAOc,QAAQ;QACf,OAAO;IACT;AACF,EAAC"}
@@ -1,8 +1,7 @@
1
1
  import crypto from 'crypto';
2
2
  import { describe, expect, it } from 'vitest';
3
3
  import { authenticateLocalStrategy } from './authenticate.js';
4
- // Helper to generate hash/salt like Payload does
5
- const generateHashAndSalt = (password)=>{
4
+ const generateExistingHashAndSalt = (password)=>{
6
5
  const salt = crypto.randomBytes(32).toString('hex');
7
6
  const hash = crypto.pbkdf2Sync(password, salt, 25000, 512, 'sha256').toString('hex');
8
7
  return {
@@ -10,10 +9,35 @@ const generateHashAndSalt = (password)=>{
10
9
  salt
11
10
  };
12
11
  };
12
+ const generateCurrentHashAndSalt = (password)=>{
13
+ const salt = crypto.randomBytes(32).toString('hex');
14
+ const hash = crypto.pbkdf2Sync(password, salt, 600000, 32, 'sha256').toString('hex');
15
+ return {
16
+ hash: `pbkdf2-sha256-v1:${hash}`,
17
+ salt
18
+ };
19
+ };
13
20
  describe('authenticateLocalStrategy', ()=>{
14
- it('should return doc when password is valid', async ()=>{
21
+ it('should return doc for an existing password hash', async ()=>{
22
+ const password = 'test-password';
23
+ const { hash, salt } = generateExistingHashAndSalt(password);
24
+ const doc = {
25
+ id: 1,
26
+ hash,
27
+ salt
28
+ };
29
+ const result = await authenticateLocalStrategy({
30
+ doc,
31
+ password
32
+ });
33
+ expect(result).toEqual({
34
+ doc,
35
+ shouldUpdatePasswordHash: true
36
+ });
37
+ });
38
+ it('should return doc for a current password hash', async ()=>{
15
39
  const password = 'test-password';
16
- const { hash, salt } = generateHashAndSalt(password);
40
+ const { hash, salt } = generateCurrentHashAndSalt(password);
17
41
  const doc = {
18
42
  id: 1,
19
43
  hash,
@@ -23,10 +47,13 @@ describe('authenticateLocalStrategy', ()=>{
23
47
  doc,
24
48
  password
25
49
  });
26
- expect(result).toEqual(doc);
50
+ expect(result).toEqual({
51
+ doc,
52
+ shouldUpdatePasswordHash: false
53
+ });
27
54
  });
28
55
  it('should return null when password is invalid', async ()=>{
29
- const { hash, salt } = generateHashAndSalt('correct-password');
56
+ const { hash, salt } = generateExistingHashAndSalt('correct-password');
30
57
  const doc = {
31
58
  id: 1,
32
59
  hash,
@@ -39,7 +66,7 @@ describe('authenticateLocalStrategy', ()=>{
39
66
  expect(result).toBeNull();
40
67
  });
41
68
  it('should return null when salt is missing', async ()=>{
42
- const { hash } = generateHashAndSalt('test-password');
69
+ const { hash } = generateExistingHashAndSalt('test-password');
43
70
  const doc = {
44
71
  id: 1,
45
72
  hash
@@ -51,7 +78,7 @@ describe('authenticateLocalStrategy', ()=>{
51
78
  expect(result).toBeNull();
52
79
  });
53
80
  it('should return null when hash is missing', async ()=>{
54
- const { salt } = generateHashAndSalt('test-password');
81
+ const { salt } = generateExistingHashAndSalt('test-password');
55
82
  const doc = {
56
83
  id: 1,
57
84
  salt
@@ -64,8 +91,8 @@ describe('authenticateLocalStrategy', ()=>{
64
91
  });
65
92
  it('should return null when hash has different length (tampered)', async ()=>{
66
93
  const password = 'test-password';
67
- const { salt } = generateHashAndSalt(password);
68
- // Truncated hash - different length than expected 512 bytes
94
+ const { salt } = generateExistingHashAndSalt(password);
95
+ // Truncated hash - different length than an existing hash derives
69
96
  const shortHash = 'abcd1234';
70
97
  const doc = {
71
98
  id: 1,