@10x-media/form-builder 0.1.0-beta.2 → 0.1.0-beta.21

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 (619) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/README.md +7 -5
  3. package/dist/actions/assertNoBlockCollision.js +18 -0
  4. package/dist/actions/assertNoBlockCollision.js.map +1 -0
  5. package/dist/actions/body/converters.d.ts +20 -0
  6. package/dist/actions/body/converters.js +57 -0
  7. package/dist/actions/body/converters.js.map +1 -0
  8. package/dist/actions/body/escapeHtml.d.ts +6 -0
  9. package/dist/actions/body/escapeHtml.js +14 -0
  10. package/dist/actions/body/escapeHtml.js.map +1 -0
  11. package/dist/actions/body/serializeBody.d.ts +55 -0
  12. package/dist/actions/body/serializeBody.js +78 -0
  13. package/dist/actions/body/serializeBody.js.map +1 -0
  14. package/dist/actions/body/serializeSlate.js +31 -0
  15. package/dist/actions/body/serializeSlate.js.map +1 -0
  16. package/dist/actions/body/textOfBody.d.ts +13 -0
  17. package/dist/actions/body/textOfBody.js +35 -0
  18. package/dist/actions/body/textOfBody.js.map +1 -0
  19. package/dist/actions/body/wildcards.d.ts +10 -0
  20. package/dist/actions/body/wildcards.js +33 -0
  21. package/dist/actions/body/wildcards.js.map +1 -0
  22. package/dist/actions/buildActionBlocks.js +12 -9
  23. package/dist/actions/buildActionBlocks.js.map +1 -1
  24. package/dist/actions/builtin/confirmation.js +47 -43
  25. package/dist/actions/builtin/confirmation.js.map +1 -1
  26. package/dist/actions/builtin/emailAction.d.ts +20 -0
  27. package/dist/actions/builtin/emailAction.js +120 -0
  28. package/dist/actions/builtin/emailAction.js.map +1 -0
  29. package/dist/actions/builtin/emailTeam.js +20 -38
  30. package/dist/actions/builtin/emailTeam.js.map +1 -1
  31. package/dist/actions/builtin/index.d.ts +16 -1
  32. package/dist/actions/builtin/index.js +21 -7
  33. package/dist/actions/builtin/index.js.map +1 -1
  34. package/dist/actions/builtin/signedWebhook.js +6 -2
  35. package/dist/actions/builtin/signedWebhook.js.map +1 -1
  36. package/dist/actions/defineAction.d.ts +17 -5
  37. package/dist/actions/defineAction.js.map +1 -1
  38. package/dist/actions/dispatch.js +51 -6
  39. package/dist/actions/dispatch.js.map +1 -1
  40. package/dist/actions/dispatchContext.js +13 -0
  41. package/dist/actions/dispatchContext.js.map +1 -0
  42. package/dist/actions/emailRecipients.d.ts +12 -0
  43. package/dist/actions/emailRecipients.js +146 -0
  44. package/dist/actions/emailRecipients.js.map +1 -0
  45. package/dist/actions/fromAddresses.d.ts +37 -0
  46. package/dist/actions/fromAddresses.js +131 -0
  47. package/dist/actions/fromAddresses.js.map +1 -0
  48. package/dist/actions/recipientSources.d.ts +34 -0
  49. package/dist/actions/recipientSources.js +11 -0
  50. package/dist/actions/recipientSources.js.map +1 -0
  51. package/dist/actions/registry.d.ts +2 -1
  52. package/dist/actions/registry.js +5 -7
  53. package/dist/actions/registry.js.map +1 -1
  54. package/dist/actions/runActions.js +10 -1
  55. package/dist/actions/runActions.js.map +1 -1
  56. package/dist/actions/task.js +47 -16
  57. package/dist/actions/task.js.map +1 -1
  58. package/dist/aggregation/aggregateResponses.d.ts +9 -0
  59. package/dist/aggregation/aggregateResponses.js +8 -14
  60. package/dist/aggregation/aggregateResponses.js.map +1 -1
  61. package/dist/aggregation/resolveResultsRequest.d.ts +41 -6
  62. package/dist/aggregation/resolveResultsRequest.js +97 -11
  63. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  64. package/dist/calc/computeCalcFields.d.ts +5 -3
  65. package/dist/calc/computeCalcFields.js +6 -4
  66. package/dist/calc/computeCalcFields.js.map +1 -1
  67. package/dist/calc/evaluate.d.ts +26 -3
  68. package/dist/calc/evaluate.js +34 -13
  69. package/dist/calc/evaluate.js.map +1 -1
  70. package/dist/calc/formatCalc.d.ts +11 -0
  71. package/dist/calc/formatCalc.js +35 -0
  72. package/dist/calc/formatCalc.js.map +1 -0
  73. package/dist/calc/formatCalcValue.d.ts +17 -0
  74. package/dist/calc/formatCalcValue.js +17 -0
  75. package/dist/calc/formatCalcValue.js.map +1 -0
  76. package/dist/calc/normalizeCalc.d.ts +17 -3
  77. package/dist/calc/normalizeCalc.js +42 -33
  78. package/dist/calc/normalizeCalc.js.map +1 -1
  79. package/dist/calc/registry.d.ts +43 -0
  80. package/dist/calc/registry.js +27 -0
  81. package/dist/calc/registry.js.map +1 -0
  82. package/dist/calc/resolveCalcContext.d.ts +36 -0
  83. package/dist/calc/resolveCalcContext.js +87 -0
  84. package/dist/calc/resolveCalcContext.js.map +1 -0
  85. package/dist/calc/types.d.ts +25 -4
  86. package/dist/calc/types.js +23 -0
  87. package/dist/calc/types.js.map +1 -0
  88. package/dist/client/ByteSizeField.d.ts +29 -0
  89. package/dist/client/ByteSizeField.js +58 -0
  90. package/dist/client/ByteSizeField.js.map +1 -0
  91. package/dist/client/CalcExpressionBuilder.d.ts +20 -0
  92. package/dist/client/CalcExpressionBuilder.js +890 -0
  93. package/dist/client/CalcExpressionBuilder.js.map +1 -0
  94. package/dist/client/ClosePollButton.d.ts +15 -0
  95. package/dist/client/ClosePollButton.js +80 -0
  96. package/dist/client/ClosePollButton.js.map +1 -0
  97. package/dist/client/CondensedArray.d.ts +15 -0
  98. package/dist/client/CondensedArray.js +97 -0
  99. package/dist/client/CondensedArray.js.map +1 -0
  100. package/dist/client/ConditionBuilder.js +2 -1
  101. package/dist/client/ConditionBuilder.js.map +1 -1
  102. package/dist/client/ConsentBlockLabel.d.ts +15 -0
  103. package/dist/client/ConsentBlockLabel.js +71 -0
  104. package/dist/client/ConsentBlockLabel.js.map +1 -0
  105. package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
  106. package/dist/client/ConsentSourceRowLabel.js +21 -0
  107. package/dist/client/ConsentSourceRowLabel.js.map +1 -0
  108. package/dist/client/EndpointOptionsSelect.d.ts +55 -0
  109. package/dist/client/EndpointOptionsSelect.js +112 -0
  110. package/dist/client/EndpointOptionsSelect.js.map +1 -0
  111. package/dist/client/FieldBlockLabel.d.ts +18 -0
  112. package/dist/client/FieldBlockLabel.js +34 -0
  113. package/dist/client/FieldBlockLabel.js.map +1 -0
  114. package/dist/client/FieldNameSelect.d.ts +37 -0
  115. package/dist/client/FieldNameSelect.js +72 -0
  116. package/dist/client/FieldNameSelect.js.map +1 -0
  117. package/dist/client/FlowBuilder.d.ts +2 -2
  118. package/dist/client/FlowBuilder.js +137 -88
  119. package/dist/client/FlowBuilder.js.map +1 -1
  120. package/dist/client/FormConditionField.js +1 -4
  121. package/dist/client/FormConditionField.js.map +1 -1
  122. package/dist/client/RecipientsSelect.d.ts +37 -0
  123. package/dist/client/RecipientsSelect.js +197 -0
  124. package/dist/client/RecipientsSelect.js.map +1 -0
  125. package/dist/client/RuleDescription.d.ts +21 -0
  126. package/dist/client/RuleDescription.js +19 -0
  127. package/dist/client/RuleDescription.js.map +1 -0
  128. package/dist/client/admin.css +557 -0
  129. package/dist/client/endpointOptions.d.ts +11 -0
  130. package/dist/client/endpointOptions.js +38 -0
  131. package/dist/client/endpointOptions.js.map +1 -0
  132. package/dist/client/flowAuthoring.js +142 -0
  133. package/dist/client/flowAuthoring.js.map +1 -0
  134. package/dist/client/synthesizeClientField.js +21 -10
  135. package/dist/client/synthesizeClientField.js.map +1 -1
  136. package/dist/client/toStaticLabel.js +13 -0
  137. package/dist/client/toStaticLabel.js.map +1 -0
  138. package/dist/collections/buttonFields.d.ts +34 -0
  139. package/dist/collections/buttonFields.js +35 -0
  140. package/dist/collections/buttonFields.js.map +1 -0
  141. package/dist/collections/cells/FieldCountCell.d.ts +15 -0
  142. package/dist/collections/cells/FieldCountCell.js +18 -0
  143. package/dist/collections/cells/FieldCountCell.js.map +1 -0
  144. package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
  145. package/dist/collections/cells/FlowStepsCell.js +17 -0
  146. package/dist/collections/cells/FlowStepsCell.js.map +1 -0
  147. package/dist/collections/formSubmissions.js +114 -28
  148. package/dist/collections/formSubmissions.js.map +1 -1
  149. package/dist/collections/forms.js +449 -112
  150. package/dist/collections/forms.js.map +1 -1
  151. package/dist/collections/formsEndpoints.js +106 -0
  152. package/dist/collections/formsEndpoints.js.map +1 -0
  153. package/dist/collections/redirectFields.d.ts +29 -0
  154. package/dist/collections/settingsFields.d.ts +31 -0
  155. package/dist/collections/settingsFields.js +45 -0
  156. package/dist/collections/settingsFields.js.map +1 -0
  157. package/dist/conditions/conditionType.js +9 -2
  158. package/dist/conditions/conditionType.js.map +1 -1
  159. package/dist/conditions/evaluate.js +1 -1
  160. package/dist/conditions/evaluate.js.map +1 -1
  161. package/dist/conditions/fieldTypes.d.ts +3 -1
  162. package/dist/conditions/fieldTypes.js +4 -1
  163. package/dist/conditions/fieldTypes.js.map +1 -1
  164. package/dist/conditions/normalizeConditions.js +20 -6
  165. package/dist/conditions/normalizeConditions.js.map +1 -1
  166. package/dist/consent/applyConsentStatements.d.ts +14 -0
  167. package/dist/consent/applyConsentStatements.js +17 -0
  168. package/dist/consent/applyConsentStatements.js.map +1 -0
  169. package/dist/consent/captureConsent.d.ts +39 -11
  170. package/dist/consent/captureConsent.js +63 -13
  171. package/dist/consent/captureConsent.js.map +1 -1
  172. package/dist/consent/consentSourcesField.d.ts +66 -0
  173. package/dist/consent/consentSourcesField.js +98 -0
  174. package/dist/consent/consentSourcesField.js.map +1 -0
  175. package/dist/consent/effectiveStatement.js +16 -0
  176. package/dist/consent/effectiveStatement.js.map +1 -0
  177. package/dist/consent/resolveConsentEntries.d.ts +24 -0
  178. package/dist/consent/resolveConsentEntries.js +40 -0
  179. package/dist/consent/resolveConsentEntries.js.map +1 -0
  180. package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
  181. package/dist/consent/resolveConsentSourcesRequest.js +54 -0
  182. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
  183. package/dist/consent/resolveConsentStatements.d.ts +45 -0
  184. package/dist/consent/resolveConsentStatements.js +46 -0
  185. package/dist/consent/resolveConsentStatements.js.map +1 -0
  186. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
  187. package/dist/consent/resolvePublishedVersionRef.js +13 -10
  188. package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
  189. package/dist/consent/types.d.ts +69 -0
  190. package/dist/context/formContext.d.ts +30 -0
  191. package/dist/context/formContext.js +54 -0
  192. package/dist/context/formContext.js.map +1 -0
  193. package/dist/email/departments.d.ts +59 -0
  194. package/dist/email/departments.js +87 -0
  195. package/dist/email/departments.js.map +1 -0
  196. package/dist/email/departmentsField.d.ts +53 -0
  197. package/dist/email/departmentsField.js +73 -0
  198. package/dist/email/departmentsField.js.map +1 -0
  199. package/dist/exports/client.d.ts +14 -1
  200. package/dist/exports/client.js +14 -1
  201. package/dist/exports/i18n.d.ts +5 -2
  202. package/dist/exports/i18n.js +5 -2
  203. package/dist/exports/react.d.ts +34 -10
  204. package/dist/exports/react.js +24 -4
  205. package/dist/exports/rsc.d.ts +10 -1
  206. package/dist/exports/rsc.js +10 -1
  207. package/dist/exports/types.d.ts +20 -12
  208. package/dist/fields/buildFieldBlocks.js +46 -25
  209. package/dist/fields/buildFieldBlocks.js.map +1 -1
  210. package/dist/fields/builtin/calculation.d.ts +17 -0
  211. package/dist/fields/builtin/calculation.js +193 -18
  212. package/dist/fields/builtin/calculation.js.map +1 -1
  213. package/dist/fields/builtin/checkbox.js +23 -0
  214. package/dist/fields/builtin/checkbox.js.map +1 -1
  215. package/dist/fields/builtin/consent.js +49 -14
  216. package/dist/fields/builtin/consent.js.map +1 -1
  217. package/dist/fields/builtin/country.d.ts +13 -0
  218. package/dist/fields/builtin/country.js +30 -0
  219. package/dist/fields/builtin/country.js.map +1 -0
  220. package/dist/fields/builtin/date.js +31 -0
  221. package/dist/fields/builtin/date.js.map +1 -0
  222. package/dist/fields/builtin/email.js +8 -1
  223. package/dist/fields/builtin/email.js.map +1 -1
  224. package/dist/fields/builtin/file.d.ts +15 -0
  225. package/dist/fields/builtin/file.js +95 -8
  226. package/dist/fields/builtin/file.js.map +1 -1
  227. package/dist/fields/builtin/index.d.ts +30 -0
  228. package/dist/fields/builtin/index.js +37 -10
  229. package/dist/fields/builtin/index.js.map +1 -1
  230. package/dist/fields/builtin/message.js +30 -0
  231. package/dist/fields/builtin/message.js.map +1 -0
  232. package/dist/fields/builtin/repeater.js +8 -3
  233. package/dist/fields/builtin/repeater.js.map +1 -1
  234. package/dist/fields/builtin/select.js +28 -4
  235. package/dist/fields/builtin/select.js.map +1 -1
  236. package/dist/fields/builtin/state.d.ts +13 -0
  237. package/dist/fields/builtin/state.js +30 -0
  238. package/dist/fields/builtin/state.js.map +1 -0
  239. package/dist/fields/builtin/text.js +7 -0
  240. package/dist/fields/builtin/text.js.map +1 -1
  241. package/dist/fields/data/regions.d.ts +21 -0
  242. package/dist/fields/data/regions.js +1238 -0
  243. package/dist/fields/data/regions.js.map +1 -0
  244. package/dist/fields/fieldKey.d.ts +15 -0
  245. package/dist/fields/fieldKey.js +16 -0
  246. package/dist/fields/fieldKey.js.map +1 -0
  247. package/dist/fields/fieldNamesOfType.js +30 -0
  248. package/dist/fields/fieldNamesOfType.js.map +1 -0
  249. package/dist/fields/instanceOptions.js +20 -0
  250. package/dist/fields/instanceOptions.js.map +1 -0
  251. package/dist/fields/localizedIf.d.ts +13 -0
  252. package/dist/fields/localizedIf.js +12 -0
  253. package/dist/fields/localizedIf.js.map +1 -0
  254. package/dist/fields/registry.d.ts +3 -1
  255. package/dist/fields/registry.js +23 -8
  256. package/dist/fields/registry.js.map +1 -1
  257. package/dist/fields/sharedConfig.js +125 -59
  258. package/dist/fields/sharedConfig.js.map +1 -1
  259. package/dist/fields/types.d.ts +78 -9
  260. package/dist/flow/engine.d.ts +6 -4
  261. package/dist/flow/engine.js +11 -6
  262. package/dist/flow/engine.js.map +1 -1
  263. package/dist/flow/normalizeFlow.js +28 -9
  264. package/dist/flow/normalizeFlow.js.map +1 -1
  265. package/dist/flow/stepLabel.js +15 -0
  266. package/dist/flow/stepLabel.js.map +1 -0
  267. package/dist/flow/types.d.ts +12 -3
  268. package/dist/flow/types.js +11 -0
  269. package/dist/flow/types.js.map +1 -0
  270. package/dist/form/pollState.d.ts +23 -0
  271. package/dist/form/pollState.js +17 -0
  272. package/dist/form/pollState.js.map +1 -0
  273. package/dist/form/toFormDocument.d.ts +110 -0
  274. package/dist/form/toFormDocument.js +89 -0
  275. package/dist/form/toFormDocument.js.map +1 -0
  276. package/dist/form/types.d.ts +73 -0
  277. package/dist/index.d.ts +67 -61
  278. package/dist/index.js +120 -25
  279. package/dist/index.js.map +1 -1
  280. package/dist/options.d.ts +281 -0
  281. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  282. package/dist/plugin/applyRegistryConfig.js +20 -0
  283. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  284. package/dist/plugin/customState.js +26 -0
  285. package/dist/plugin/customState.js.map +1 -0
  286. package/dist/plugin/registerCollections.js +43 -40
  287. package/dist/plugin/registerCollections.js.map +1 -1
  288. package/dist/plugin/uploadsCollection.d.ts +16 -0
  289. package/dist/plugin/uploadsCollection.js +53 -0
  290. package/dist/plugin/uploadsCollection.js.map +1 -0
  291. package/dist/poll/applyPollOptions.js +18 -0
  292. package/dist/poll/applyPollOptions.js.map +1 -0
  293. package/dist/poll/buildPollOptionSourceFields.js +50 -0
  294. package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
  295. package/dist/poll/closeJob.d.ts +53 -0
  296. package/dist/poll/closeJob.js +121 -0
  297. package/dist/poll/closeJob.js.map +1 -0
  298. package/dist/poll/definePollOptionSource.d.ts +44 -0
  299. package/dist/poll/definePollOptionSource.js +6 -0
  300. package/dist/poll/definePollOptionSource.js.map +1 -0
  301. package/dist/poll/definePollType.d.ts +34 -0
  302. package/dist/poll/definePollType.js +6 -0
  303. package/dist/poll/definePollType.js.map +1 -0
  304. package/dist/poll/effectivePollOptions.d.ts +38 -0
  305. package/dist/poll/effectivePollOptions.js +64 -0
  306. package/dist/poll/effectivePollOptions.js.map +1 -0
  307. package/dist/poll/mostVoted.d.ts +21 -0
  308. package/dist/poll/mostVoted.js +32 -0
  309. package/dist/poll/mostVoted.js.map +1 -0
  310. package/dist/poll/outcomeBeforeChange.js +91 -0
  311. package/dist/poll/outcomeBeforeChange.js.map +1 -0
  312. package/dist/poll/outcomeFields.d.ts +38 -0
  313. package/dist/poll/outcomeFields.js +52 -0
  314. package/dist/poll/outcomeFields.js.map +1 -0
  315. package/dist/poll/pollTypeRegistry.d.ts +34 -0
  316. package/dist/poll/pollTypeRegistry.js +69 -0
  317. package/dist/poll/pollTypeRegistry.js.map +1 -0
  318. package/dist/poll/registry.d.ts +17 -0
  319. package/dist/poll/registry.js +12 -0
  320. package/dist/poll/registry.js.map +1 -0
  321. package/dist/poll/resolvePollCloseRequest.js +106 -0
  322. package/dist/poll/resolvePollCloseRequest.js.map +1 -0
  323. package/dist/poll/resolvePollOptions.d.ts +29 -0
  324. package/dist/poll/resolvePollOptions.js +52 -0
  325. package/dist/poll/resolvePollOptions.js.map +1 -0
  326. package/dist/poll/resolvePollOptionsRequest.js +50 -0
  327. package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
  328. package/dist/poll/resolvePollOutcome.d.ts +40 -0
  329. package/dist/poll/resolvePollOutcome.js +90 -0
  330. package/dist/poll/resolvePollOutcome.js.map +1 -0
  331. package/dist/poll/resultsField.js +21 -0
  332. package/dist/poll/resultsField.js.map +1 -0
  333. package/dist/poll/votes/aggregateFromVotes.d.ts +27 -0
  334. package/dist/poll/votes/aggregateFromVotes.js +54 -0
  335. package/dist/poll/votes/aggregateFromVotes.js.map +1 -0
  336. package/dist/poll/votes/answerValues.js +16 -0
  337. package/dist/poll/votes/answerValues.js.map +1 -0
  338. package/dist/poll/votes/bumpPollVote.js +88 -0
  339. package/dist/poll/votes/bumpPollVote.js.map +1 -0
  340. package/dist/poll/votes/recountPollVotes.d.ts +20 -0
  341. package/dist/poll/votes/recountPollVotes.js +63 -0
  342. package/dist/poll/votes/recountPollVotes.js.map +1 -0
  343. package/dist/poll/votes/voteTallyHook.js +76 -0
  344. package/dist/poll/votes/voteTallyHook.js.map +1 -0
  345. package/dist/poll/votes/votesCollection.d.ts +19 -0
  346. package/dist/poll/votes/votesCollection.js +89 -0
  347. package/dist/poll/votes/votesCollection.js.map +1 -0
  348. package/dist/prefill/valuesFromSearchParams.js +5 -9
  349. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  350. package/dist/presentations/types.d.ts +3 -2
  351. package/dist/react/Form.d.ts +77 -35
  352. package/dist/react/Form.js +368 -222
  353. package/dist/react/Form.js.map +1 -1
  354. package/dist/react/FormContext.d.ts +51 -1
  355. package/dist/react/FormContext.js.map +1 -1
  356. package/dist/react/FormControls.d.ts +55 -0
  357. package/dist/react/FormControls.js +56 -0
  358. package/dist/react/FormControls.js.map +1 -0
  359. package/dist/react/FormFields.d.ts +18 -0
  360. package/dist/react/FormFields.js +114 -0
  361. package/dist/react/FormFields.js.map +1 -0
  362. package/dist/react/FormLayout.d.ts +4 -2
  363. package/dist/react/FormLayout.js +3 -2
  364. package/dist/react/FormLayout.js.map +1 -1
  365. package/dist/react/FormResults.d.ts +8 -3
  366. package/dist/react/FormResults.js +50 -29
  367. package/dist/react/FormResults.js.map +1 -1
  368. package/dist/react/FormSteps.d.ts +17 -0
  369. package/dist/react/FormSteps.js +35 -0
  370. package/dist/react/FormSteps.js.map +1 -0
  371. package/dist/react/Poll.d.ts +40 -6
  372. package/dist/react/Poll.js +137 -33
  373. package/dist/react/Poll.js.map +1 -1
  374. package/dist/react/adapters.d.ts +35 -0
  375. package/dist/react/adapters.js +11 -0
  376. package/dist/react/adapters.js.map +1 -0
  377. package/dist/react/captcha/HcaptchaCaptcha.d.ts +31 -0
  378. package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
  379. package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
  380. package/dist/react/captcha/RecaptchaCaptcha.d.ts +44 -0
  381. package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
  382. package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
  383. package/dist/react/captcha/TurnstileCaptcha.d.ts +31 -0
  384. package/dist/react/captcha/TurnstileCaptcha.js +53 -0
  385. package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
  386. package/dist/react/captcha/types.d.ts +8 -0
  387. package/dist/react/captcha/useCaptchaScript.js +63 -0
  388. package/dist/react/captcha/useCaptchaScript.js.map +1 -0
  389. package/dist/react/captcha/vendors.js +8 -0
  390. package/dist/react/captcha/vendors.js.map +1 -0
  391. package/dist/react/contract.d.ts +2 -3
  392. package/dist/react/contract.js.map +1 -1
  393. package/dist/react/fetchResults.d.ts +1 -1
  394. package/dist/react/fetchResults.js +1 -1
  395. package/dist/react/fetchResults.js.map +1 -1
  396. package/dist/react/presentation/DialogSurface.js +1 -4
  397. package/dist/react/presentation/DialogSurface.js.map +1 -1
  398. package/dist/react/primitives/Checkbox.d.ts +6 -2
  399. package/dist/react/primitives/Checkbox.js +4 -2
  400. package/dist/react/primitives/Checkbox.js.map +1 -1
  401. package/dist/react/primitives/ChoiceGroup.d.ts +40 -0
  402. package/dist/react/primitives/ChoiceGroup.js +33 -0
  403. package/dist/react/primitives/ChoiceGroup.js.map +1 -0
  404. package/dist/react/primitives/FieldShell.d.ts +13 -5
  405. package/dist/react/primitives/FieldShell.js +35 -32
  406. package/dist/react/primitives/FieldShell.js.map +1 -1
  407. package/dist/react/primitives/Input.d.ts +4 -2
  408. package/dist/react/primitives/Input.js +2 -1
  409. package/dist/react/primitives/Input.js.map +1 -1
  410. package/dist/react/renderers/calculation.js +7 -5
  411. package/dist/react/renderers/calculation.js.map +1 -1
  412. package/dist/react/renderers/checkbox.js +3 -5
  413. package/dist/react/renderers/checkbox.js.map +1 -1
  414. package/dist/react/renderers/consent.js +84 -36
  415. package/dist/react/renderers/consent.js.map +1 -1
  416. package/dist/react/renderers/date.js +33 -0
  417. package/dist/react/renderers/date.js.map +1 -0
  418. package/dist/react/renderers/email.js +4 -5
  419. package/dist/react/renderers/email.js.map +1 -1
  420. package/dist/react/renderers/file.js +29 -14
  421. package/dist/react/renderers/file.js.map +1 -1
  422. package/dist/react/renderers/index.js +7 -0
  423. package/dist/react/renderers/index.js.map +1 -1
  424. package/dist/react/renderers/message.js +41 -0
  425. package/dist/react/renderers/message.js.map +1 -0
  426. package/dist/react/renderers/number.js +1 -4
  427. package/dist/react/renderers/number.js.map +1 -1
  428. package/dist/react/renderers/region.js +43 -0
  429. package/dist/react/renderers/region.js.map +1 -0
  430. package/dist/react/renderers/repeater.js +32 -14
  431. package/dist/react/renderers/repeater.js.map +1 -1
  432. package/dist/react/renderers/select.js +21 -6
  433. package/dist/react/renderers/select.js.map +1 -1
  434. package/dist/react/renderers/text.js +4 -5
  435. package/dist/react/renderers/text.js.map +1 -1
  436. package/dist/react/renderers/textarea.js +1 -4
  437. package/dist/react/renderers/textarea.js.map +1 -1
  438. package/dist/react/state.d.ts +46 -3
  439. package/dist/react/state.js +73 -10
  440. package/dist/react/state.js.map +1 -1
  441. package/dist/react/submitForm.js +1 -1
  442. package/dist/react/submitForm.js.map +1 -1
  443. package/dist/react/uploadFile.d.ts +2 -2
  444. package/dist/react/uploadFile.js +1 -1
  445. package/dist/react/uploadFile.js.map +1 -1
  446. package/dist/react/useCalcValues.d.ts +10 -0
  447. package/dist/react/useCalcValues.js +13 -0
  448. package/dist/react/useCalcValues.js.map +1 -0
  449. package/dist/react/useField.d.ts +0 -1
  450. package/dist/react/useField.js +7 -6
  451. package/dist/react/useField.js.map +1 -1
  452. package/dist/react/useFormState.d.ts +1 -1
  453. package/dist/react/useFormState.js +1 -1
  454. package/dist/react/useFormState.js.map +1 -1
  455. package/dist/react/validateField.js +3 -6
  456. package/dist/react/validateField.js.map +1 -1
  457. package/dist/recall/interpolate.d.ts +4 -3
  458. package/dist/recall/interpolate.js +5 -4
  459. package/dist/recall/interpolate.js.map +1 -1
  460. package/dist/recall/resolver.d.ts +1 -1
  461. package/dist/recall/resolver.js +15 -3
  462. package/dist/recall/resolver.js.map +1 -1
  463. package/dist/spam/captcha.d.ts +4 -3
  464. package/dist/spam/captcha.js +4 -3
  465. package/dist/spam/captcha.js.map +1 -1
  466. package/dist/spam/clientIp.js +9 -0
  467. package/dist/spam/clientIp.js.map +1 -0
  468. package/dist/spam/constants.js +13 -1
  469. package/dist/spam/constants.js.map +1 -1
  470. package/dist/spam/identify.js +3 -6
  471. package/dist/spam/identify.js.map +1 -1
  472. package/dist/spam/providers/hcaptcha.d.ts +17 -0
  473. package/dist/spam/providers/hcaptcha.js +18 -0
  474. package/dist/spam/providers/hcaptcha.js.map +1 -0
  475. package/dist/spam/providers/recaptcha.d.ts +23 -0
  476. package/dist/spam/providers/recaptcha.js +23 -0
  477. package/dist/spam/providers/recaptcha.js.map +1 -0
  478. package/dist/spam/providers/siteverify.js +34 -0
  479. package/dist/spam/providers/siteverify.js.map +1 -0
  480. package/dist/spam/providers/turnstile.d.ts +17 -0
  481. package/dist/spam/providers/turnstile.js +18 -0
  482. package/dist/spam/providers/turnstile.js.map +1 -0
  483. package/dist/spam/resolveSpam.js +2 -1
  484. package/dist/spam/resolveSpam.js.map +1 -1
  485. package/dist/spam/spamGuard.js +25 -11
  486. package/dist/spam/spamGuard.js.map +1 -1
  487. package/dist/spam/types.d.ts +19 -4
  488. package/dist/submissions/SubmissionAnswers.js +6 -2
  489. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  490. package/dist/submissions/SubmissionAnswersClient.js +100 -64
  491. package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
  492. package/dist/submissions/coerceBoolean.js +14 -0
  493. package/dist/submissions/coerceBoolean.js.map +1 -0
  494. package/dist/submissions/createSubmission.d.ts +36 -0
  495. package/dist/submissions/createSubmission.js +26 -0
  496. package/dist/submissions/createSubmission.js.map +1 -0
  497. package/dist/submissions/formIdOf.js +13 -0
  498. package/dist/submissions/formIdOf.js.map +1 -0
  499. package/dist/submissions/resolveVotedSubmission.d.ts +27 -0
  500. package/dist/submissions/resolveVotedSubmission.js +51 -0
  501. package/dist/submissions/resolveVotedSubmission.js.map +1 -0
  502. package/dist/submissions/runSubmission.d.ts +10 -0
  503. package/dist/submissions/runSubmission.js +104 -54
  504. package/dist/submissions/runSubmission.js.map +1 -1
  505. package/dist/submissions/types.d.ts +11 -4
  506. package/dist/submissions/validateSubmission.js +99 -13
  507. package/dist/submissions/validateSubmission.js.map +1 -1
  508. package/dist/submissions/verifyContext.js +35 -0
  509. package/dist/submissions/verifyContext.js.map +1 -0
  510. package/dist/submissions/voteChangeEndpoint.js +105 -0
  511. package/dist/submissions/voteChangeEndpoint.js.map +1 -0
  512. package/dist/submissions/votedCookie.d.ts +15 -0
  513. package/dist/submissions/votedCookie.js +74 -0
  514. package/dist/submissions/votedCookie.js.map +1 -0
  515. package/dist/translations/clientRuntimeKeys.d.ts +17 -0
  516. package/dist/translations/clientRuntimeKeys.js +48 -0
  517. package/dist/translations/clientRuntimeKeys.js.map +1 -0
  518. package/dist/translations/de.d.ts +12 -0
  519. package/dist/translations/de.js +380 -0
  520. package/dist/translations/de.js.map +1 -0
  521. package/dist/translations/en.js +250 -28
  522. package/dist/translations/en.js.map +1 -1
  523. package/dist/translations/index.d.ts +14 -5
  524. package/dist/translations/index.js +14 -3
  525. package/dist/translations/index.js.map +1 -1
  526. package/dist/translations/keys.d.ts +245 -23
  527. package/dist/translations/keys.js +246 -24
  528. package/dist/translations/keys.js.map +1 -1
  529. package/dist/translations/makeTranslate.d.ts +7 -3
  530. package/dist/translations/makeTranslate.js +12 -3
  531. package/dist/translations/makeTranslate.js.map +1 -1
  532. package/dist/translations/server.js +8 -1
  533. package/dist/translations/server.js.map +1 -1
  534. package/dist/uploads/captureFileRef.d.ts +10 -3
  535. package/dist/uploads/captureFileRef.js +18 -9
  536. package/dist/uploads/captureFileRef.js.map +1 -1
  537. package/dist/uploads/formatBytes.d.ts +9 -0
  538. package/dist/uploads/formatBytes.js +25 -0
  539. package/dist/uploads/formatBytes.js.map +1 -0
  540. package/dist/uploads/resolveFileRef.js +3 -2
  541. package/dist/uploads/resolveFileRef.js.map +1 -1
  542. package/dist/uploads/types.d.ts +1 -2
  543. package/dist/validation/buildRuleBlocks.js +18 -17
  544. package/dist/validation/buildRuleBlocks.js.map +1 -1
  545. package/dist/validation/builtin/email.js +2 -1
  546. package/dist/validation/builtin/email.js.map +1 -1
  547. package/dist/validation/builtin/index.d.ts +14 -0
  548. package/dist/validation/builtin/index.js +14 -1
  549. package/dist/validation/builtin/index.js.map +1 -1
  550. package/dist/validation/builtin/integer.js +15 -0
  551. package/dist/validation/builtin/integer.js.map +1 -0
  552. package/dist/validation/builtin/matchesField.js +3 -7
  553. package/dist/validation/builtin/matchesField.js.map +1 -1
  554. package/dist/validation/builtin/max.js +1 -0
  555. package/dist/validation/builtin/max.js.map +1 -1
  556. package/dist/validation/builtin/maxDate.js +24 -0
  557. package/dist/validation/builtin/maxDate.js.map +1 -0
  558. package/dist/validation/builtin/maxLength.js +1 -0
  559. package/dist/validation/builtin/maxLength.js.map +1 -1
  560. package/dist/validation/builtin/min.js +1 -0
  561. package/dist/validation/builtin/min.js.map +1 -1
  562. package/dist/validation/builtin/minDate.js +24 -0
  563. package/dist/validation/builtin/minDate.js.map +1 -0
  564. package/dist/validation/builtin/minLength.js +1 -0
  565. package/dist/validation/builtin/minLength.js.map +1 -1
  566. package/dist/validation/builtin/notAlreadySubmitted.js +5 -1
  567. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  568. package/dist/validation/builtin/oneOf.js +1 -0
  569. package/dist/validation/builtin/oneOf.js.map +1 -1
  570. package/dist/validation/builtin/pattern.js +11 -2
  571. package/dist/validation/builtin/pattern.js.map +1 -1
  572. package/dist/validation/builtin/url.js +1 -0
  573. package/dist/validation/builtin/url.js.map +1 -1
  574. package/dist/validation/builtin/validateDateBound.js +18 -0
  575. package/dist/validation/builtin/validateDateBound.js.map +1 -0
  576. package/dist/validation/builtin/validateRegexParams.js +44 -0
  577. package/dist/validation/builtin/validateRegexParams.js.map +1 -0
  578. package/dist/validation/emailPattern.js +10 -0
  579. package/dist/validation/emailPattern.js.map +1 -0
  580. package/dist/validation/fieldTargetParam.d.ts +22 -0
  581. package/dist/validation/fieldTargetParam.js +38 -0
  582. package/dist/validation/fieldTargetParam.js.map +1 -0
  583. package/dist/validation/registry.d.ts +2 -1
  584. package/dist/validation/registry.js +2 -9
  585. package/dist/validation/registry.js.map +1 -1
  586. package/dist/validation/runValidation.js +8 -29
  587. package/dist/validation/runValidation.js.map +1 -1
  588. package/dist/validation/types.d.ts +5 -11
  589. package/dist/validation/validateUrl.js +22 -0
  590. package/dist/validation/validateUrl.js.map +1 -0
  591. package/package.json +14 -6
  592. package/styles/form-builder.css +94 -190
  593. package/dist/collections/uploads.d.ts +0 -30
  594. package/dist/collections/uploads.js +0 -55
  595. package/dist/collections/uploads.js.map +0 -1
  596. package/dist/consent/buildConsentSourceConfig.js +0 -49
  597. package/dist/consent/buildConsentSourceConfig.js.map +0 -1
  598. package/dist/consent/builtin/index.d.ts +0 -7
  599. package/dist/consent/builtin/index.js +0 -11
  600. package/dist/consent/builtin/index.js.map +0 -1
  601. package/dist/consent/builtin/pageReference.js +0 -75
  602. package/dist/consent/builtin/pageReference.js.map +0 -1
  603. package/dist/consent/builtin/static.js +0 -44
  604. package/dist/consent/builtin/static.js.map +0 -1
  605. package/dist/consent/defineConsentSource.d.ts +0 -35
  606. package/dist/consent/defineConsentSource.js +0 -6
  607. package/dist/consent/defineConsentSource.js.map +0 -1
  608. package/dist/consent/registry.d.ts +0 -16
  609. package/dist/consent/registry.js +0 -16
  610. package/dist/consent/registry.js.map +0 -1
  611. package/dist/consent/resolveConsentLinks.d.ts +0 -19
  612. package/dist/consent/resolveConsentLinks.js +0 -26
  613. package/dist/consent/resolveConsentLinks.js.map +0 -1
  614. package/dist/presentations/registry.d.ts +0 -15
  615. package/dist/presentations/registry.js +0 -15
  616. package/dist/presentations/registry.js.map +0 -1
  617. package/dist/react/toFormDocument.d.ts +0 -24
  618. package/dist/react/toFormDocument.js +0 -22
  619. package/dist/react/toFormDocument.js.map +0 -1
@@ -7,9 +7,21 @@
7
7
  const DEFAULT_HONEYPOT_FIELD = "website";
8
8
  /** Reserved `values` entry carrying a captcha token from the client. */
9
9
  const CAPTCHA_TOKEN_KEY = "__fb_captcha";
10
+ /**
11
+ * Reserved `values` entry carrying a signed form-context token (see `context/formContext.ts`). Rides
12
+ * alongside the answers like the captcha token, but is verified and stored separately: it is never an
13
+ * answer and must not appear in `values`, the answers view, or `{{*}}` body output.
14
+ */
15
+ const CONTEXT_KEY = "__fb_ctx";
16
+ /**
17
+ * Reserved `values` entry the honeypot decoy is submitted under. The decoy's DOM input keeps a
18
+ * plausible cosmetic name (e.g. `website`), but the submitted value rides this fixed key so a real
19
+ * field sharing that name is never stripped or mistaken for the decoy.
20
+ */
21
+ const HONEYPOT_VALUE_KEY = "__fb_hp";
10
22
  /** `req.context` key under which the spam guard stashes the resolved identity for upload-ownership verification. */
11
23
  const IDENTITY_CONTEXT_KEY = "formBuilderSpamIdentity";
12
24
  //#endregion
13
- export { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD, IDENTITY_CONTEXT_KEY };
25
+ export { CAPTCHA_TOKEN_KEY, CONTEXT_KEY, DEFAULT_HONEYPOT_FIELD, HONEYPOT_VALUE_KEY, IDENTITY_CONTEXT_KEY };
14
26
 
15
27
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../src/spam/constants.ts"],"sourcesContent":["/**\n * Default honeypot decoy field name. \"website\" is plausible to bots but doesn't trigger Chrome's\n * email-address autofill (unlike names containing \"email\"). Bots fill every input; real users\n * never see the hidden field.\n */\nexport const DEFAULT_HONEYPOT_FIELD = 'website'\n\n/** Reserved `values` entry carrying a captcha token from the client. */\nexport const CAPTCHA_TOKEN_KEY = '__fb_captcha'\n\n/** `req.context` key under which the spam guard stashes the resolved identity for upload-ownership verification. */\nexport const IDENTITY_CONTEXT_KEY = 'formBuilderSpamIdentity'\n"],"mappings":";;;;;;AAKA,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;AAGjC,MAAa,uBAAuB"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../../src/spam/constants.ts"],"sourcesContent":["/**\n * Default honeypot decoy field name. \"website\" is plausible to bots but doesn't trigger Chrome's\n * email-address autofill (unlike names containing \"email\"). Bots fill every input; real users\n * never see the hidden field.\n */\nexport const DEFAULT_HONEYPOT_FIELD = 'website'\n\n/** Reserved `values` entry carrying a captcha token from the client. */\nexport const CAPTCHA_TOKEN_KEY = '__fb_captcha'\n\n/**\n * Reserved `values` entry carrying a signed form-context token (see `context/formContext.ts`). Rides\n * alongside the answers like the captcha token, but is verified and stored separately: it is never an\n * answer and must not appear in `values`, the answers view, or `{{*}}` body output.\n */\nexport const CONTEXT_KEY = '__fb_ctx'\n\n/**\n * Reserved `values` entry the honeypot decoy is submitted under. The decoy's DOM input keeps a\n * plausible cosmetic name (e.g. `website`), but the submitted value rides this fixed key so a real\n * field sharing that name is never stripped or mistaken for the decoy.\n */\nexport const HONEYPOT_VALUE_KEY = '__fb_hp'\n\n/** `req.context` key under which the spam guard stashes the resolved identity for upload-ownership verification. */\nexport const IDENTITY_CONTEXT_KEY = 'formBuilderSpamIdentity'\n"],"mappings":";;;;;;AAKA,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;;;;;AAOjC,MAAa,cAAc;;;;;;AAO3B,MAAa,qBAAqB;;AAGlC,MAAa,uBAAuB"}
@@ -1,3 +1,4 @@
1
+ import { firstHop } from "./clientIp.js";
1
2
  //#region src/spam/identify.ts
2
3
  /**
3
4
  * Default identity resolution for rate-limiting + upload ownership. Prefers an authenticated user id
@@ -7,12 +8,8 @@
7
8
  const defaultIdentify = (ipHeader) => (req) => {
8
9
  const userId = req.user?.id;
9
10
  if (userId != null) return `user:${String(userId)}`;
10
- const header = req.headers?.get(ipHeader);
11
- if (header) {
12
- const first = header.split(",")[0]?.trim();
13
- if (first) return `ip:${first}`;
14
- }
15
- return null;
11
+ const ip = firstHop(req.headers, ipHeader);
12
+ return ip ? `ip:${ip}` : null;
16
13
  };
17
14
  //#endregion
18
15
  export { defaultIdentify };
@@ -1 +1 @@
1
- {"version":3,"file":"identify.js","names":[],"sources":["../../src/spam/identify.ts"],"sourcesContent":["import type { IdentifyFn } from './types'\n\n/**\n * Default identity resolution for rate-limiting + upload ownership. Prefers an authenticated user id\n * (trustworthy); else the first hop of the configured trusted IP header (best-effort, proxy-dependent);\n * else null, in which case the caller fails open (cannot fairly rate-limit an unidentifiable client).\n */\nexport const defaultIdentify =\n\t(ipHeader: string): IdentifyFn =>\n\t(req) => {\n\t\tconst userId = req.user?.id\n\t\tif (userId != null) {\n\t\t\treturn `user:${String(userId)}`\n\t\t}\n\t\tconst header = req.headers?.get(ipHeader)\n\t\tif (header) {\n\t\t\tconst first = header.split(',')[0]?.trim()\n\t\t\tif (first) {\n\t\t\t\treturn `ip:${first}`\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n"],"mappings":";;;;;;AAOA,MAAa,mBACX,cACA,QAAQ;CACR,MAAM,SAAS,IAAI,MAAM;CACzB,IAAI,UAAU,MACb,OAAO,QAAQ,OAAO,MAAM;CAE7B,MAAM,SAAS,IAAI,SAAS,IAAI,QAAQ;CACxC,IAAI,QAAQ;EACX,MAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,IAAI,KAAK;EACzC,IAAI,OACH,OAAO,MAAM;CAEf;CACA,OAAO;AACR"}
1
+ {"version":3,"file":"identify.js","names":[],"sources":["../../src/spam/identify.ts"],"sourcesContent":["import { firstHop } from './clientIp'\nimport type { IdentifyFn } from './types'\n\n/**\n * Default identity resolution for rate-limiting + upload ownership. Prefers an authenticated user id\n * (trustworthy); else the first hop of the configured trusted IP header (best-effort, proxy-dependent);\n * else null, in which case the caller fails open (cannot fairly rate-limit an unidentifiable client).\n */\nexport const defaultIdentify =\n\t(ipHeader: string): IdentifyFn =>\n\t(req) => {\n\t\tconst userId = req.user?.id\n\t\tif (userId != null) {\n\t\t\treturn `user:${String(userId)}`\n\t\t}\n\t\tconst ip = firstHop(req.headers, ipHeader)\n\t\treturn ip ? `ip:${ip}` : null\n\t}\n"],"mappings":";;;;;;;AAQA,MAAa,mBACX,cACA,QAAQ;CACR,MAAM,SAAS,IAAI,MAAM;CACzB,IAAI,UAAU,MACb,OAAO,QAAQ,OAAO,MAAM;CAE7B,MAAM,KAAK,SAAS,IAAI,SAAS,QAAQ;CACzC,OAAO,KAAK,MAAM,OAAO;AAC1B"}
@@ -0,0 +1,17 @@
1
+ import { CaptchaProvider } from "../types.js";
2
+
3
+ //#region src/spam/providers/hcaptcha.d.ts
4
+ type HcaptchaProviderOptions = {
5
+ /** hCaptcha secret key (server-side, never the site key). */secretKey: string; /** Override the siteverify endpoint. */
6
+ verifyUrl?: string; /** Header read for the client IP forwarded as `remoteip`. Default 'x-forwarded-for'. */
7
+ ipHeader?: string; /** Verification request timeout in ms. Default 5000. */
8
+ timeoutMs?: number;
9
+ };
10
+ /**
11
+ * hCaptcha verification adapter. Network errors, timeouts, and non-2xx responses fail closed
12
+ * (the submission is rejected).
13
+ */
14
+ declare const hcaptchaProvider: (options: HcaptchaProviderOptions) => CaptchaProvider;
15
+ //#endregion
16
+ export { HcaptchaProviderOptions, hcaptchaProvider };
17
+ //# sourceMappingURL=hcaptcha.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { defineCaptchaProvider } from "../captcha.js";
2
+ import { postSiteverify, siteverifyParams } from "./siteverify.js";
3
+ //#region src/spam/providers/hcaptcha.ts
4
+ const HCAPTCHA_VERIFY_URL = "https://api.hcaptcha.com/siteverify";
5
+ /**
6
+ * hCaptcha verification adapter. Network errors, timeouts, and non-2xx responses fail closed
7
+ * (the submission is rejected).
8
+ */
9
+ const hcaptchaProvider = (options) => defineCaptchaProvider({
10
+ type: "hcaptcha",
11
+ verify: async ({ token, req }) => {
12
+ return (await postSiteverify(options.verifyUrl ?? HCAPTCHA_VERIFY_URL, siteverifyParams(options, token, req), options.timeoutMs ?? 5e3))?.success === true;
13
+ }
14
+ });
15
+ //#endregion
16
+ export { hcaptchaProvider };
17
+
18
+ //# sourceMappingURL=hcaptcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hcaptcha.js","names":[],"sources":["../../../src/spam/providers/hcaptcha.ts"],"sourcesContent":["import { defineCaptchaProvider } from '../captcha'\nimport type { CaptchaProvider } from '../types'\nimport { DEFAULT_SITEVERIFY_TIMEOUT, postSiteverify, siteverifyParams } from './siteverify'\n\nconst HCAPTCHA_VERIFY_URL = 'https://api.hcaptcha.com/siteverify'\n\nexport type HcaptchaProviderOptions = {\n\t/** hCaptcha secret key (server-side, never the site key). */\n\tsecretKey: string\n\t/** Override the siteverify endpoint. */\n\tverifyUrl?: string\n\t/** Header read for the client IP forwarded as `remoteip`. Default 'x-forwarded-for'. */\n\tipHeader?: string\n\t/** Verification request timeout in ms. Default 5000. */\n\ttimeoutMs?: number\n}\n\n/**\n * hCaptcha verification adapter. Network errors, timeouts, and non-2xx responses fail closed\n * (the submission is rejected).\n */\nexport const hcaptchaProvider = (options: HcaptchaProviderOptions): CaptchaProvider =>\n\tdefineCaptchaProvider({\n\t\ttype: 'hcaptcha',\n\t\tverify: async ({ token, req }) => {\n\t\t\tconst result = await postSiteverify(\n\t\t\t\toptions.verifyUrl ?? HCAPTCHA_VERIFY_URL,\n\t\t\t\tsiteverifyParams(options, token, req),\n\t\t\t\toptions.timeoutMs ?? DEFAULT_SITEVERIFY_TIMEOUT\n\t\t\t)\n\t\t\treturn result?.success === true\n\t\t},\n\t})\n"],"mappings":";;;AAIA,MAAM,sBAAsB;;;;;AAiB5B,MAAa,oBAAoB,YAChC,sBAAsB;CACrB,MAAM;CACN,QAAQ,OAAO,EAAE,OAAO,UAAU;EAMjC,QAAO,MALc,eACpB,QAAQ,aAAa,qBACrB,iBAAiB,SAAS,OAAO,GAAG,GACpC,QAAQ,aAAA,GACT,IACe,YAAY;CAC5B;AACD,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { CaptchaProvider } from "../types.js";
2
+
3
+ //#region src/spam/providers/recaptcha.d.ts
4
+ type RecaptchaProviderOptions = {
5
+ /** reCAPTCHA secret key (server-side, never the site key). */secretKey: string;
6
+ /**
7
+ * Minimum acceptable v3 score (0 to 1). Applied only when the response carries a score,
8
+ * defaulting to 0.5; v2 responses have no score and pass on `success` alone.
9
+ */
10
+ minScore?: number; /** Override the siteverify endpoint. */
11
+ verifyUrl?: string; /** Header read for the client IP forwarded as `remoteip`. Default 'x-forwarded-for'. */
12
+ ipHeader?: string; /** Verification request timeout in ms. Default 5000. */
13
+ timeoutMs?: number;
14
+ };
15
+ /**
16
+ * Google reCAPTCHA verification adapter for both v2 (checkbox) and v3 (score) tokens. A v3
17
+ * response must also meet `minScore`. Network errors, timeouts, and non-2xx responses fail
18
+ * closed (the submission is rejected).
19
+ */
20
+ declare const recaptchaProvider: (options: RecaptchaProviderOptions) => CaptchaProvider;
21
+ //#endregion
22
+ export { RecaptchaProviderOptions, recaptchaProvider };
23
+ //# sourceMappingURL=recaptcha.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { defineCaptchaProvider } from "../captcha.js";
2
+ import { postSiteverify, siteverifyParams } from "./siteverify.js";
3
+ //#region src/spam/providers/recaptcha.ts
4
+ const RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify";
5
+ const DEFAULT_MIN_SCORE = .5;
6
+ /**
7
+ * Google reCAPTCHA verification adapter for both v2 (checkbox) and v3 (score) tokens. A v3
8
+ * response must also meet `minScore`. Network errors, timeouts, and non-2xx responses fail
9
+ * closed (the submission is rejected).
10
+ */
11
+ const recaptchaProvider = (options) => defineCaptchaProvider({
12
+ type: "recaptcha",
13
+ verify: async ({ token, req }) => {
14
+ const result = await postSiteverify(options.verifyUrl ?? RECAPTCHA_VERIFY_URL, siteverifyParams(options, token, req), options.timeoutMs ?? 5e3);
15
+ if (result?.success !== true) return false;
16
+ if (typeof result.score === "number") return result.score >= (options.minScore ?? DEFAULT_MIN_SCORE);
17
+ return true;
18
+ }
19
+ });
20
+ //#endregion
21
+ export { recaptchaProvider };
22
+
23
+ //# sourceMappingURL=recaptcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recaptcha.js","names":[],"sources":["../../../src/spam/providers/recaptcha.ts"],"sourcesContent":["import { defineCaptchaProvider } from '../captcha'\nimport type { CaptchaProvider } from '../types'\nimport { DEFAULT_SITEVERIFY_TIMEOUT, postSiteverify, siteverifyParams } from './siteverify'\n\nconst RECAPTCHA_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'\nconst DEFAULT_MIN_SCORE = 0.5\n\nexport type RecaptchaProviderOptions = {\n\t/** reCAPTCHA secret key (server-side, never the site key). */\n\tsecretKey: string\n\t/**\n\t * Minimum acceptable v3 score (0 to 1). Applied only when the response carries a score,\n\t * defaulting to 0.5; v2 responses have no score and pass on `success` alone.\n\t */\n\tminScore?: number\n\t/** Override the siteverify endpoint. */\n\tverifyUrl?: string\n\t/** Header read for the client IP forwarded as `remoteip`. Default 'x-forwarded-for'. */\n\tipHeader?: string\n\t/** Verification request timeout in ms. Default 5000. */\n\ttimeoutMs?: number\n}\n\n/**\n * Google reCAPTCHA verification adapter for both v2 (checkbox) and v3 (score) tokens. A v3\n * response must also meet `minScore`. Network errors, timeouts, and non-2xx responses fail\n * closed (the submission is rejected).\n */\nexport const recaptchaProvider = (options: RecaptchaProviderOptions): CaptchaProvider =>\n\tdefineCaptchaProvider({\n\t\ttype: 'recaptcha',\n\t\tverify: async ({ token, req }) => {\n\t\t\tconst result = await postSiteverify(\n\t\t\t\toptions.verifyUrl ?? RECAPTCHA_VERIFY_URL,\n\t\t\t\tsiteverifyParams(options, token, req),\n\t\t\t\toptions.timeoutMs ?? DEFAULT_SITEVERIFY_TIMEOUT\n\t\t\t)\n\t\t\tif (result?.success !== true) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (typeof result.score === 'number') {\n\t\t\t\treturn result.score >= (options.minScore ?? DEFAULT_MIN_SCORE)\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t})\n"],"mappings":";;;AAIA,MAAM,uBAAuB;AAC7B,MAAM,oBAAoB;;;;;;AAuB1B,MAAa,qBAAqB,YACjC,sBAAsB;CACrB,MAAM;CACN,QAAQ,OAAO,EAAE,OAAO,UAAU;EACjC,MAAM,SAAS,MAAM,eACpB,QAAQ,aAAa,sBACrB,iBAAiB,SAAS,OAAO,GAAG,GACpC,QAAQ,aAAA,GACT;EACA,IAAI,QAAQ,YAAY,MACvB,OAAO;EAER,IAAI,OAAO,OAAO,UAAU,UAC3B,OAAO,OAAO,UAAU,QAAQ,YAAY;EAE7C,OAAO;CACR;AACD,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { firstHop } from "../clientIp.js";
2
+ //#region src/spam/providers/siteverify.ts
3
+ const DEFAULT_SITEVERIFY_TIMEOUT = 5e3;
4
+ /** Form params for a siteverify POST: secret + response, plus `remoteip` when the IP header resolves. */
5
+ const siteverifyParams = (args, token, req) => {
6
+ const params = new URLSearchParams({
7
+ secret: args.secretKey,
8
+ response: token
9
+ });
10
+ const remoteip = firstHop(req.headers, args.ipHeader ?? "x-forwarded-for");
11
+ if (remoteip) params.set("remoteip", remoteip);
12
+ return params;
13
+ };
14
+ /**
15
+ * POST a form-encoded siteverify request. Network errors, timeouts, and non-2xx responses all
16
+ * resolve to null so adapters fail closed instead of throwing into the submission path.
17
+ */
18
+ const postSiteverify = async (url, params, timeoutMs) => {
19
+ try {
20
+ const res = await fetch(url, {
21
+ method: "POST",
22
+ body: params,
23
+ signal: AbortSignal.timeout(timeoutMs)
24
+ });
25
+ if (!res.ok) return null;
26
+ return await res.json();
27
+ } catch {
28
+ return null;
29
+ }
30
+ };
31
+ //#endregion
32
+ export { DEFAULT_SITEVERIFY_TIMEOUT, postSiteverify, siteverifyParams };
33
+
34
+ //# sourceMappingURL=siteverify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"siteverify.js","names":[],"sources":["../../../src/spam/providers/siteverify.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport { firstHop } from '../clientIp'\n\n/** The response shape shared by the Turnstile, reCAPTCHA, and hCaptcha siteverify endpoints. */\nexport type SiteverifyResult = {\n\tsuccess?: boolean\n\tscore?: number\n\t'error-codes'?: string[]\n}\n\nexport const DEFAULT_SITEVERIFY_TIMEOUT = 5_000\n\nexport type SiteverifyParamsArgs = {\n\tsecretKey: string\n\t/** Header read for the client IP forwarded as `remoteip`. Default 'x-forwarded-for'. */\n\tipHeader?: string\n}\n\n/** Form params for a siteverify POST: secret + response, plus `remoteip` when the IP header resolves. */\nexport const siteverifyParams = (\n\targs: SiteverifyParamsArgs,\n\ttoken: string,\n\treq: PayloadRequest\n): URLSearchParams => {\n\tconst params = new URLSearchParams({ secret: args.secretKey, response: token })\n\tconst remoteip = firstHop(req.headers, args.ipHeader ?? 'x-forwarded-for')\n\tif (remoteip) {\n\t\tparams.set('remoteip', remoteip)\n\t}\n\treturn params\n}\n\n/**\n * POST a form-encoded siteverify request. Network errors, timeouts, and non-2xx responses all\n * resolve to null so adapters fail closed instead of throwing into the submission path.\n */\nexport const postSiteverify = async (\n\turl: string,\n\tparams: URLSearchParams,\n\ttimeoutMs: number\n): Promise<SiteverifyResult | null> => {\n\ttry {\n\t\tconst res = await fetch(url, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: params,\n\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t})\n\t\tif (!res.ok) {\n\t\t\treturn null\n\t\t}\n\t\treturn (await res.json()) as SiteverifyResult\n\t} catch {\n\t\treturn null\n\t}\n}\n"],"mappings":";;AAUA,MAAa,6BAA6B;;AAS1C,MAAa,oBACZ,MACA,OACA,QACqB;CACrB,MAAM,SAAS,IAAI,gBAAgB;EAAE,QAAQ,KAAK;EAAW,UAAU;CAAM,CAAC;CAC9E,MAAM,WAAW,SAAS,IAAI,SAAS,KAAK,YAAY,iBAAiB;CACzE,IAAI,UACH,OAAO,IAAI,YAAY,QAAQ;CAEhC,OAAO;AACR;;;;;AAMA,MAAa,iBAAiB,OAC7B,KACA,QACA,cACsC;CACtC,IAAI;EACH,MAAM,MAAM,MAAM,MAAM,KAAK;GAC5B,QAAQ;GACR,MAAM;GACN,QAAQ,YAAY,QAAQ,SAAS;EACtC,CAAC;EACD,IAAI,CAAC,IAAI,IACR,OAAO;EAER,OAAQ,MAAM,IAAI,KAAK;CACxB,QAAQ;EACP,OAAO;CACR;AACD"}
@@ -0,0 +1,17 @@
1
+ import { CaptchaProvider } from "../types.js";
2
+
3
+ //#region src/spam/providers/turnstile.d.ts
4
+ type TurnstileProviderOptions = {
5
+ /** Turnstile secret key (server-side, never the site key). */secretKey: string; /** Override the siteverify endpoint. */
6
+ verifyUrl?: string; /** Header read for the client IP forwarded as `remoteip`. Default 'x-forwarded-for'. */
7
+ ipHeader?: string; /** Verification request timeout in ms. Default 5000. */
8
+ timeoutMs?: number;
9
+ };
10
+ /**
11
+ * Cloudflare Turnstile verification adapter. Network errors, timeouts, and non-2xx responses
12
+ * fail closed (the submission is rejected).
13
+ */
14
+ declare const turnstileProvider: (options: TurnstileProviderOptions) => CaptchaProvider;
15
+ //#endregion
16
+ export { TurnstileProviderOptions, turnstileProvider };
17
+ //# sourceMappingURL=turnstile.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { defineCaptchaProvider } from "../captcha.js";
2
+ import { postSiteverify, siteverifyParams } from "./siteverify.js";
3
+ //#region src/spam/providers/turnstile.ts
4
+ const TURNSTILE_VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
5
+ /**
6
+ * Cloudflare Turnstile verification adapter. Network errors, timeouts, and non-2xx responses
7
+ * fail closed (the submission is rejected).
8
+ */
9
+ const turnstileProvider = (options) => defineCaptchaProvider({
10
+ type: "turnstile",
11
+ verify: async ({ token, req }) => {
12
+ return (await postSiteverify(options.verifyUrl ?? TURNSTILE_VERIFY_URL, siteverifyParams(options, token, req), options.timeoutMs ?? 5e3))?.success === true;
13
+ }
14
+ });
15
+ //#endregion
16
+ export { turnstileProvider };
17
+
18
+ //# sourceMappingURL=turnstile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turnstile.js","names":[],"sources":["../../../src/spam/providers/turnstile.ts"],"sourcesContent":["import { defineCaptchaProvider } from '../captcha'\nimport type { CaptchaProvider } from '../types'\nimport { DEFAULT_SITEVERIFY_TIMEOUT, postSiteverify, siteverifyParams } from './siteverify'\n\nconst TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'\n\nexport type TurnstileProviderOptions = {\n\t/** Turnstile secret key (server-side, never the site key). */\n\tsecretKey: string\n\t/** Override the siteverify endpoint. */\n\tverifyUrl?: string\n\t/** Header read for the client IP forwarded as `remoteip`. Default 'x-forwarded-for'. */\n\tipHeader?: string\n\t/** Verification request timeout in ms. Default 5000. */\n\ttimeoutMs?: number\n}\n\n/**\n * Cloudflare Turnstile verification adapter. Network errors, timeouts, and non-2xx responses\n * fail closed (the submission is rejected).\n */\nexport const turnstileProvider = (options: TurnstileProviderOptions): CaptchaProvider =>\n\tdefineCaptchaProvider({\n\t\ttype: 'turnstile',\n\t\tverify: async ({ token, req }) => {\n\t\t\tconst result = await postSiteverify(\n\t\t\t\toptions.verifyUrl ?? TURNSTILE_VERIFY_URL,\n\t\t\t\tsiteverifyParams(options, token, req),\n\t\t\t\toptions.timeoutMs ?? DEFAULT_SITEVERIFY_TIMEOUT\n\t\t\t)\n\t\t\treturn result?.success === true\n\t\t},\n\t})\n"],"mappings":";;;AAIA,MAAM,uBAAuB;;;;;AAiB7B,MAAa,qBAAqB,YACjC,sBAAsB;CACrB,MAAM;CACN,QAAQ,OAAO,EAAE,OAAO,UAAU;EAMjC,QAAO,MALc,eACpB,QAAQ,aAAa,sBACrB,iBAAiB,SAAS,OAAO,GAAG,GACpC,QAAQ,aAAA,GACT,IACe,YAAY;CAC5B;AACD,CAAC"}
@@ -33,7 +33,8 @@ const resolveSpamConfig = (option) => {
33
33
  metadata: {
34
34
  ip: cfg.metadata?.ip ?? false,
35
35
  ua: cfg.metadata?.ua ?? false
36
- }
36
+ },
37
+ uploadOwnership: cfg.uploadOwnership ?? "lenient"
37
38
  };
38
39
  };
39
40
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"resolveSpam.js","names":[],"sources":["../../src/spam/resolveSpam.ts"],"sourcesContent":["import { DEFAULT_HONEYPOT_FIELD } from './constants'\nimport { defaultIdentify } from './identify'\nimport { createKvRateLimiter } from './rateLimiter'\nimport type {\n\tRateLimitConfig,\n\tResolvedRateLimit,\n\tResolvedSpamConfig,\n\tSpamConfig,\n\tSpamOption,\n} from './types'\n\nconst DEFAULT_WINDOW = 60_000\nconst DEFAULT_SUBMISSION_MAX = 5\nconst DEFAULT_UPLOAD_MAX = 20\n\nconst resolveRateLimit = (\n\toption: false | RateLimitConfig | undefined,\n\tdefaultMax: number\n): false | ResolvedRateLimit => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg = option ?? {}\n\treturn {\n\t\twindow: cfg.window ?? DEFAULT_WINDOW,\n\t\tmax: cfg.max ?? defaultMax,\n\t\tlimiter: cfg.limiter ?? createKvRateLimiter(),\n\t}\n}\n\n/**\n * Resolve the `spam` plugin option into a concrete config. `false` disables the whole subsystem.\n * Otherwise honeypot + both rate limits default on, captcha + metadata capture default off, and the\n * identity seam defaults to user id / trusted IP header.\n */\nexport const resolveSpamConfig = (option: SpamOption | undefined): ResolvedSpamConfig | false => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg: SpamConfig = option ?? {}\n\tconst ipHeader = cfg.ipHeader ?? 'x-forwarded-for'\n\treturn {\n\t\thoneypot:\n\t\t\tcfg.honeypot === false\n\t\t\t\t? false\n\t\t\t\t: { fieldName: cfg.honeypot?.fieldName ?? DEFAULT_HONEYPOT_FIELD },\n\t\trateLimit: resolveRateLimit(cfg.rateLimit, DEFAULT_SUBMISSION_MAX),\n\t\tuploadRateLimit: resolveRateLimit(cfg.uploadRateLimit, DEFAULT_UPLOAD_MAX),\n\t\tcaptcha: cfg.captcha,\n\t\tidentify: cfg.identify ?? defaultIdentify(ipHeader),\n\t\tipHeader,\n\t\tmetadata: { ip: cfg.metadata?.ip ?? false, ua: cfg.metadata?.ua ?? false },\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,iBAAiB;AACvB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAE3B,MAAM,oBACL,QACA,eAC+B;CAC/B,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAM,UAAU,CAAC;CACvB,OAAO;EACN,QAAQ,IAAI,UAAU;EACtB,KAAK,IAAI,OAAO;EAChB,SAAS,IAAI,WAAW,oBAAoB;CAC7C;AACD;;;;;;AAOA,MAAa,qBAAqB,WAA+D;CAChG,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAkB,UAAU,CAAC;CACnC,MAAM,WAAW,IAAI,YAAY;CACjC,OAAO;EACN,UACC,IAAI,aAAa,QACd,QACA,EAAE,WAAW,IAAI,UAAU,aAAA,UAAoC;EACnE,WAAW,iBAAiB,IAAI,WAAW,sBAAsB;EACjE,iBAAiB,iBAAiB,IAAI,iBAAiB,kBAAkB;EACzE,SAAS,IAAI;EACb,UAAU,IAAI,YAAY,gBAAgB,QAAQ;EAClD;EACA,UAAU;GAAE,IAAI,IAAI,UAAU,MAAM;GAAO,IAAI,IAAI,UAAU,MAAM;EAAM;CAC1E;AACD"}
1
+ {"version":3,"file":"resolveSpam.js","names":[],"sources":["../../src/spam/resolveSpam.ts"],"sourcesContent":["import { DEFAULT_HONEYPOT_FIELD } from './constants'\nimport { defaultIdentify } from './identify'\nimport { createKvRateLimiter } from './rateLimiter'\nimport type {\n\tRateLimitConfig,\n\tResolvedRateLimit,\n\tResolvedSpamConfig,\n\tSpamConfig,\n\tSpamOption,\n} from './types'\n\nconst DEFAULT_WINDOW = 60_000\nconst DEFAULT_SUBMISSION_MAX = 5\nconst DEFAULT_UPLOAD_MAX = 20\n\nconst resolveRateLimit = (\n\toption: false | RateLimitConfig | undefined,\n\tdefaultMax: number\n): false | ResolvedRateLimit => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg = option ?? {}\n\treturn {\n\t\twindow: cfg.window ?? DEFAULT_WINDOW,\n\t\tmax: cfg.max ?? defaultMax,\n\t\tlimiter: cfg.limiter ?? createKvRateLimiter(),\n\t}\n}\n\n/**\n * Resolve the `spam` plugin option into a concrete config. `false` disables the whole subsystem.\n * Otherwise honeypot + both rate limits default on, captcha + metadata capture default off, and the\n * identity seam defaults to user id / trusted IP header.\n */\nexport const resolveSpamConfig = (option: SpamOption | undefined): ResolvedSpamConfig | false => {\n\tif (option === false) {\n\t\treturn false\n\t}\n\tconst cfg: SpamConfig = option ?? {}\n\tconst ipHeader = cfg.ipHeader ?? 'x-forwarded-for'\n\treturn {\n\t\thoneypot:\n\t\t\tcfg.honeypot === false\n\t\t\t\t? false\n\t\t\t\t: { fieldName: cfg.honeypot?.fieldName ?? DEFAULT_HONEYPOT_FIELD },\n\t\trateLimit: resolveRateLimit(cfg.rateLimit, DEFAULT_SUBMISSION_MAX),\n\t\tuploadRateLimit: resolveRateLimit(cfg.uploadRateLimit, DEFAULT_UPLOAD_MAX),\n\t\tcaptcha: cfg.captcha,\n\t\tidentify: cfg.identify ?? defaultIdentify(ipHeader),\n\t\tipHeader,\n\t\tmetadata: { ip: cfg.metadata?.ip ?? false, ua: cfg.metadata?.ua ?? false },\n\t\tuploadOwnership: cfg.uploadOwnership ?? 'lenient',\n\t}\n}\n"],"mappings":";;;;AAWA,MAAM,iBAAiB;AACvB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAE3B,MAAM,oBACL,QACA,eAC+B;CAC/B,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAM,UAAU,CAAC;CACvB,OAAO;EACN,QAAQ,IAAI,UAAU;EACtB,KAAK,IAAI,OAAO;EAChB,SAAS,IAAI,WAAW,oBAAoB;CAC7C;AACD;;;;;;AAOA,MAAa,qBAAqB,WAA+D;CAChG,IAAI,WAAW,OACd,OAAO;CAER,MAAM,MAAkB,UAAU,CAAC;CACnC,MAAM,WAAW,IAAI,YAAY;CACjC,OAAO;EACN,UACC,IAAI,aAAa,QACd,QACA,EAAE,WAAW,IAAI,UAAU,aAAA,UAAoC;EACnE,WAAW,iBAAiB,IAAI,WAAW,sBAAsB;EACjE,iBAAiB,iBAAiB,IAAI,iBAAiB,kBAAkB;EACzE,SAAS,IAAI;EACb,UAAU,IAAI,YAAY,gBAAgB,QAAQ;EAClD;EACA,UAAU;GAAE,IAAI,IAAI,UAAU,MAAM;GAAO,IAAI,IAAI,UAAU,MAAM;EAAM;EACzE,iBAAiB,IAAI,mBAAmB;CACzC;AACD"}
@@ -1,12 +1,17 @@
1
1
  import { keys } from "../translations/keys.js";
2
2
  import { asTranslate } from "../translations/server.js";
3
- import { IDENTITY_CONTEXT_KEY } from "./constants.js";
3
+ import { voteChangeTargetOf } from "../submissions/votedCookie.js";
4
+ import { firstHop } from "./clientIp.js";
5
+ import { HONEYPOT_VALUE_KEY, IDENTITY_CONTEXT_KEY } from "./constants.js";
4
6
  import { extractReservedValues, isHoneypotTripped } from "./reserved.js";
5
7
  import { APIError } from "payload";
6
8
  //#region src/spam/spamGuard.ts
7
- const firstHop = (req, header) => {
8
- const raw = req.headers?.get(header);
9
- return raw ? raw.split(",")[0]?.trim() ?? void 0 : void 0;
9
+ let warnedRateLimitSkipped = false;
10
+ /** Warn once per process when rate limiting is configured but skipped for lack of a request identity. */
11
+ const warnRateLimitSkippedOnce = (req) => {
12
+ if (warnedRateLimitSkipped) return;
13
+ warnedRateLimitSkipped = true;
14
+ req.payload.logger?.warn("@10x-media/form-builder: submission rate limiting is configured but skipped for a request with no resolvable identity (no user and no client IP). Set spam.ipHeader for your proxy or provide a custom spam.identify.");
10
15
  };
11
16
  /**
12
17
  * Submissions spam guard, prepended before `validateSubmission` in `beforeValidate` so it rejects before
@@ -18,12 +23,17 @@ const firstHop = (req, header) => {
18
23
  * complements edge/CDN/WAF limiting, not a DoS replacement.
19
24
  */
20
25
  const buildSpamGuard = (spam) => async ({ data, operation, req }) => {
21
- if (operation !== "create" || !data) return data;
26
+ if (!(operation === "create" || operation === "update" && voteChangeTargetOf(req) !== void 0) || !data) return data;
22
27
  const t = asTranslate(req.i18n.t);
23
28
  const authenticated = Boolean(req.user);
24
29
  const identity = await spam.identify(req);
25
30
  if (identity != null) req.context[IDENTITY_CONTEXT_KEY] = identity;
26
- if (spam.rateLimit !== false && identity != null) {
31
+ let rateLimitState;
32
+ if (spam.rateLimit === false) rateLimitState = "disabled";
33
+ else if (identity == null) {
34
+ rateLimitState = "skipped-no-identity";
35
+ warnRateLimitSkippedOnce(req);
36
+ } else {
27
37
  const formKey = data.form != null ? String(data.form) : "unknown";
28
38
  const { ok } = await spam.rateLimit.limiter.check({
29
39
  key: `submissions:${formKey}:${identity}`,
@@ -32,11 +42,12 @@ const buildSpamGuard = (spam) => async ({ data, operation, req }) => {
32
42
  req
33
43
  });
34
44
  if (!ok) throw new APIError(t(keys.spamRateLimited), 429);
45
+ rateLimitState = "enforced";
35
46
  }
36
- const honeypotField = spam.honeypot === false ? null : spam.honeypot.fieldName;
37
- const { cleaned, honeypot, captchaToken } = extractReservedValues(data.values ?? [], honeypotField);
47
+ const honeypotKey = spam.honeypot === false ? null : HONEYPOT_VALUE_KEY;
48
+ const { cleaned, honeypot, captchaToken } = extractReservedValues(data.values ?? [], honeypotKey);
38
49
  data.values = cleaned;
39
- if (!authenticated && honeypotField !== null && isHoneypotTripped(honeypot)) throw new APIError(t(keys.spamRejected), 400);
50
+ if (!authenticated && honeypotKey !== null && isHoneypotTripped(honeypot)) throw new APIError(t(keys.spamRejected), 400);
40
51
  const captchaChecked = Boolean(spam.captcha) && !authenticated;
41
52
  if (spam.captcha && !authenticated) {
42
53
  if (!(typeof captchaToken === "string" && captchaToken.length > 0 ? await spam.captcha.verify({
@@ -46,10 +57,13 @@ const buildSpamGuard = (spam) => async ({ data, operation, req }) => {
46
57
  }
47
58
  const serverMeta = {
48
59
  at: (/* @__PURE__ */ new Date()).toISOString(),
49
- spam: { captcha: captchaChecked ? "passed" : "skipped" }
60
+ spam: {
61
+ captcha: captchaChecked ? "passed" : "skipped",
62
+ rateLimit: rateLimitState
63
+ }
50
64
  };
51
65
  if (spam.metadata.ip) {
52
- const ip = firstHop(req, spam.ipHeader);
66
+ const ip = firstHop(req.headers, spam.ipHeader);
53
67
  if (ip) serverMeta.ip = ip;
54
68
  }
55
69
  if (spam.metadata.ua) {
@@ -1 +1 @@
1
- {"version":3,"file":"spamGuard.js","names":[],"sources":["../../src/spam/spamGuard.ts"],"sourcesContent":["import { APIError, type CollectionBeforeValidateHook } from 'payload'\nimport type { SubmissionValue } from '../submissions/types'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport { IDENTITY_CONTEXT_KEY } from './constants'\nimport { extractReservedValues, isHoneypotTripped } from './reserved'\nimport type { ResolvedSpamConfig } from './types'\n\nconst firstHop = (req: { headers?: Headers }, header: string): string | undefined => {\n\tconst raw = req.headers?.get(header)\n\treturn raw ? (raw.split(',')[0]?.trim() ?? undefined) : undefined\n}\n\n/**\n * Submissions spam guard, prepended before `validateSubmission` in `beforeValidate` so it rejects before\n * the form load + field validation. On create it: resolves the request identity once (stashing it on\n * `req.context` for upload-ownership verification downstream); rate-limits per form + identity (429,\n * fail-open when identity is null); strips reserved `values` entries (honeypot, captcha token); rejects a\n * filled honeypot with a generic error; verifies a configured captcha; and writes a server-authoritative\n * `meta` (timestamp + spam signal, with opt-in ip/ua). App-level rate limiting is defense-in-depth that\n * complements edge/CDN/WAF limiting, not a DoS replacement.\n */\nexport const buildSpamGuard =\n\t(spam: ResolvedSpamConfig): CollectionBeforeValidateHook =>\n\tasync ({ data, operation, req }) => {\n\t\tif (operation !== 'create' || !data) {\n\t\t\treturn data\n\t\t}\n\t\tconst t = asTranslate(req.i18n.t)\n\t\t// Honeypot + captcha are anti-bot measures for the anonymous public path; an authenticated request\n\t\t// (admin panel, logged-in API client) is already gated by auth and never carries a decoy/token, so\n\t\t// checking it would falsely reject legitimate authed creates. Rate-limit + ownership still apply.\n\t\tconst authenticated = Boolean(req.user)\n\n\t\tconst identity = await spam.identify(req)\n\t\tif (identity != null) {\n\t\t\treq.context[IDENTITY_CONTEXT_KEY] = identity\n\t\t}\n\n\t\tif (spam.rateLimit !== false && identity != null) {\n\t\t\tconst formKey = data.form != null ? String(data.form) : 'unknown'\n\t\t\tconst { ok } = await spam.rateLimit.limiter.check({\n\t\t\t\tkey: `submissions:${formKey}:${identity}`,\n\t\t\t\tmax: spam.rateLimit.max,\n\t\t\t\twindow: spam.rateLimit.window,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!ok) {\n\t\t\t\tthrow new APIError(t(keys.spamRateLimited), 429)\n\t\t\t}\n\t\t}\n\n\t\tconst honeypotField = spam.honeypot === false ? null : spam.honeypot.fieldName\n\t\tconst values = (data.values as SubmissionValue[] | undefined) ?? []\n\t\tconst { cleaned, honeypot, captchaToken } = extractReservedValues(values, honeypotField)\n\t\tdata.values = cleaned\n\n\t\tif (!authenticated && honeypotField !== null && isHoneypotTripped(honeypot)) {\n\t\t\tthrow new APIError(t(keys.spamRejected), 400)\n\t\t}\n\n\t\tconst captchaChecked = Boolean(spam.captcha) && !authenticated\n\t\tif (spam.captcha && !authenticated) {\n\t\t\tconst passed =\n\t\t\t\ttypeof captchaToken === 'string' && captchaToken.length > 0\n\t\t\t\t\t? await spam.captcha.verify({ token: captchaToken, req }).catch(() => false)\n\t\t\t\t\t: false\n\t\t\tif (!passed) {\n\t\t\t\tthrow new APIError(t(keys.spamCaptchaFailed), 400)\n\t\t\t}\n\t\t}\n\n\t\tconst serverMeta: Record<string, unknown> = {\n\t\t\tat: new Date().toISOString(),\n\t\t\tspam: { captcha: captchaChecked ? 'passed' : 'skipped' },\n\t\t}\n\t\tif (spam.metadata.ip) {\n\t\t\tconst ip = firstHop(req, spam.ipHeader)\n\t\t\tif (ip) {\n\t\t\t\tserverMeta.ip = ip\n\t\t\t}\n\t\t}\n\t\tif (spam.metadata.ua) {\n\t\t\tconst ua = req.headers?.get('user-agent')\n\t\t\tif (ua) {\n\t\t\t\tserverMeta.ua = ua\n\t\t\t}\n\t\t}\n\t\t// Preserve an authenticated (trusted) caller's own `meta` keys, with server fields winning. Anonymous\n\t\t// `meta` is never trusted, so it is discarded.\n\t\tconst clientMeta =\n\t\t\tauthenticated && data.meta != null && typeof data.meta === 'object'\n\t\t\t\t? (data.meta as Record<string, unknown>)\n\t\t\t\t: {}\n\t\tdata.meta = { ...clientMeta, ...serverMeta }\n\n\t\treturn data\n\t}\n"],"mappings":";;;;;;AAQA,MAAM,YAAY,KAA4B,WAAuC;CACpF,MAAM,MAAM,IAAI,SAAS,IAAI,MAAM;CACnC,OAAO,MAAO,IAAI,MAAM,GAAG,EAAE,IAAI,KAAK,KAAK,KAAA,IAAa,KAAA;AACzD;;;;;;;;;;AAWA,MAAa,kBACX,SACD,OAAO,EAAE,MAAM,WAAW,UAAU;CACnC,IAAI,cAAc,YAAY,CAAC,MAC9B,OAAO;CAER,MAAM,IAAI,YAAY,IAAI,KAAK,CAAC;CAIhC,MAAM,gBAAgB,QAAQ,IAAI,IAAI;CAEtC,MAAM,WAAW,MAAM,KAAK,SAAS,GAAG;CACxC,IAAI,YAAY,MACf,IAAI,QAAQ,wBAAwB;CAGrC,IAAI,KAAK,cAAc,SAAS,YAAY,MAAM;EACjD,MAAM,UAAU,KAAK,QAAQ,OAAO,OAAO,KAAK,IAAI,IAAI;EACxD,MAAM,EAAE,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM;GACjD,KAAK,eAAe,QAAQ,GAAG;GAC/B,KAAK,KAAK,UAAU;GACpB,QAAQ,KAAK,UAAU;GACvB;EACD,CAAC;EACD,IAAI,CAAC,IACJ,MAAM,IAAI,SAAS,EAAE,KAAK,eAAe,GAAG,GAAG;CAEjD;CAEA,MAAM,gBAAgB,KAAK,aAAa,QAAQ,OAAO,KAAK,SAAS;CAErE,MAAM,EAAE,SAAS,UAAU,iBAAiB,sBAD5B,KAAK,UAA4C,CAAC,GACQ,aAAa;CACvF,KAAK,SAAS;CAEd,IAAI,CAAC,iBAAiB,kBAAkB,QAAQ,kBAAkB,QAAQ,GACzE,MAAM,IAAI,SAAS,EAAE,KAAK,YAAY,GAAG,GAAG;CAG7C,MAAM,iBAAiB,QAAQ,KAAK,OAAO,KAAK,CAAC;CACjD,IAAI,KAAK,WAAW,CAAC;MAKhB,EAHH,OAAO,iBAAiB,YAAY,aAAa,SAAS,IACvD,MAAM,KAAK,QAAQ,OAAO;GAAE,OAAO;GAAc;EAAI,CAAC,EAAE,YAAY,KAAK,IACzE,QAEH,MAAM,IAAI,SAAS,EAAE,KAAK,iBAAiB,GAAG,GAAG;CAAA;CAInD,MAAM,aAAsC;EAC3C,qBAAI,IAAI,KAAK,GAAE,YAAY;EAC3B,MAAM,EAAE,SAAS,iBAAiB,WAAW,UAAU;CACxD;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,SAAS,KAAK,KAAK,QAAQ;EACtC,IAAI,IACH,WAAW,KAAK;CAElB;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,IAAI,SAAS,IAAI,YAAY;EACxC,IAAI,IACH,WAAW,KAAK;CAElB;CAOA,KAAK,OAAO;EAAE,GAHb,iBAAiB,KAAK,QAAQ,QAAQ,OAAO,KAAK,SAAS,WACvD,KAAK,OACN,CAAC;EACwB,GAAG;CAAW;CAE3C,OAAO;AACR"}
1
+ {"version":3,"file":"spamGuard.js","names":[],"sources":["../../src/spam/spamGuard.ts"],"sourcesContent":["import { APIError, type CollectionBeforeValidateHook, type PayloadRequest } from 'payload'\nimport type { SubmissionValue } from '../submissions/types'\nimport { voteChangeTargetOf } from '../submissions/votedCookie'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport { firstHop } from './clientIp'\nimport { HONEYPOT_VALUE_KEY, IDENTITY_CONTEXT_KEY } from './constants'\nimport { extractReservedValues, isHoneypotTripped } from './reserved'\nimport type { ResolvedSpamConfig } from './types'\n\nlet warnedRateLimitSkipped = false\n\n/** Warn once per process when rate limiting is configured but skipped for lack of a request identity. */\nconst warnRateLimitSkippedOnce = (req: PayloadRequest): void => {\n\tif (warnedRateLimitSkipped) {\n\t\treturn\n\t}\n\twarnedRateLimitSkipped = true\n\treq.payload.logger?.warn(\n\t\t'@10x-media/form-builder: submission rate limiting is configured but skipped for a request with no resolvable identity (no user and no client IP). Set spam.ipHeader for your proxy or provide a custom spam.identify.'\n\t)\n}\n\n/**\n * Submissions spam guard, prepended before `validateSubmission` in `beforeValidate` so it rejects before\n * the form load + field validation. On create it: resolves the request identity once (stashing it on\n * `req.context` for upload-ownership verification downstream); rate-limits per form + identity (429,\n * fail-open when identity is null); strips reserved `values` entries (honeypot, captcha token); rejects a\n * filled honeypot with a generic error; verifies a configured captcha; and writes a server-authoritative\n * `meta` (timestamp + spam signal, with opt-in ip/ua). App-level rate limiting is defense-in-depth that\n * complements edge/CDN/WAF limiting, not a DoS replacement.\n */\nexport const buildSpamGuard =\n\t(spam: ResolvedSpamConfig): CollectionBeforeValidateHook =>\n\tasync ({ data, operation, req }) => {\n\t\t// A flagged vote-change update is an anonymous public write like a create, so the full guard\n\t\t// (rate limit, honeypot, captcha, metadata) applies to it too; other updates stay exempt.\n\t\tconst guarded =\n\t\t\toperation === 'create' || (operation === 'update' && voteChangeTargetOf(req) !== undefined)\n\t\tif (!guarded || !data) {\n\t\t\treturn data\n\t\t}\n\t\tconst t = asTranslate(req.i18n.t)\n\t\t// Honeypot + captcha are anti-bot measures for the anonymous public path; an authenticated request\n\t\t// (admin panel, logged-in API client) is already gated by auth and never carries a decoy/token, so\n\t\t// checking it would falsely reject legitimate authed creates. Rate-limit + ownership still apply.\n\t\tconst authenticated = Boolean(req.user)\n\n\t\tconst identity = await spam.identify(req)\n\t\tif (identity != null) {\n\t\t\treq.context[IDENTITY_CONTEXT_KEY] = identity\n\t\t}\n\n\t\tlet rateLimitState: 'enforced' | 'skipped-no-identity' | 'disabled'\n\t\tif (spam.rateLimit === false) {\n\t\t\trateLimitState = 'disabled'\n\t\t} else if (identity == null) {\n\t\t\t// Fail open (an unidentifiable submitter cannot be keyed) but never silently: record it on\n\t\t\t// meta.spam and warn once, so a deployment with no proxy or a misconfigured ipHeader is visible.\n\t\t\trateLimitState = 'skipped-no-identity'\n\t\t\twarnRateLimitSkippedOnce(req)\n\t\t} else {\n\t\t\tconst formKey = data.form != null ? String(data.form) : 'unknown'\n\t\t\tconst { ok } = await spam.rateLimit.limiter.check({\n\t\t\t\tkey: `submissions:${formKey}:${identity}`,\n\t\t\t\tmax: spam.rateLimit.max,\n\t\t\t\twindow: spam.rateLimit.window,\n\t\t\t\treq,\n\t\t\t})\n\t\t\tif (!ok) {\n\t\t\t\tthrow new APIError(t(keys.spamRateLimited), 429)\n\t\t\t}\n\t\t\trateLimitState = 'enforced'\n\t\t}\n\n\t\t// The decoy rides a fixed reserved key (not its cosmetic DOM name), so a real field sharing that\n\t\t// name is never stripped or mistaken for the honeypot. `spam.honeypot.fieldName` is now only the\n\t\t// client's DOM-input name hint and no longer participates in server matching.\n\t\tconst honeypotKey = spam.honeypot === false ? null : HONEYPOT_VALUE_KEY\n\t\tconst values = (data.values as SubmissionValue[] | undefined) ?? []\n\t\tconst { cleaned, honeypot, captchaToken } = extractReservedValues(values, honeypotKey)\n\t\tdata.values = cleaned\n\n\t\tif (!authenticated && honeypotKey !== null && isHoneypotTripped(honeypot)) {\n\t\t\tthrow new APIError(t(keys.spamRejected), 400)\n\t\t}\n\n\t\tconst captchaChecked = Boolean(spam.captcha) && !authenticated\n\t\tif (spam.captcha && !authenticated) {\n\t\t\tconst passed =\n\t\t\t\ttypeof captchaToken === 'string' && captchaToken.length > 0\n\t\t\t\t\t? await spam.captcha.verify({ token: captchaToken, req }).catch(() => false)\n\t\t\t\t\t: false\n\t\t\tif (!passed) {\n\t\t\t\tthrow new APIError(t(keys.spamCaptchaFailed), 400)\n\t\t\t}\n\t\t}\n\n\t\tconst serverMeta: Record<string, unknown> = {\n\t\t\tat: new Date().toISOString(),\n\t\t\tspam: { captcha: captchaChecked ? 'passed' : 'skipped', rateLimit: rateLimitState },\n\t\t}\n\t\tif (spam.metadata.ip) {\n\t\t\tconst ip = firstHop(req.headers, spam.ipHeader)\n\t\t\tif (ip) {\n\t\t\t\tserverMeta.ip = ip\n\t\t\t}\n\t\t}\n\t\tif (spam.metadata.ua) {\n\t\t\tconst ua = req.headers?.get('user-agent')\n\t\t\tif (ua) {\n\t\t\t\tserverMeta.ua = ua\n\t\t\t}\n\t\t}\n\t\t// Preserve an authenticated (trusted) caller's own `meta` keys, with server fields winning. Anonymous\n\t\t// `meta` is never trusted, so it is discarded.\n\t\tconst clientMeta =\n\t\t\tauthenticated && data.meta != null && typeof data.meta === 'object'\n\t\t\t\t? (data.meta as Record<string, unknown>)\n\t\t\t\t: {}\n\t\tdata.meta = { ...clientMeta, ...serverMeta }\n\n\t\treturn data\n\t}\n"],"mappings":";;;;;;;;AAUA,IAAI,yBAAyB;;AAG7B,MAAM,4BAA4B,QAA8B;CAC/D,IAAI,wBACH;CAED,yBAAyB;CACzB,IAAI,QAAQ,QAAQ,KACnB,uNACD;AACD;;;;;;;;;;AAWA,MAAa,kBACX,SACD,OAAO,EAAE,MAAM,WAAW,UAAU;CAKnC,IAAI,EADH,cAAc,YAAa,cAAc,YAAY,mBAAmB,GAAG,MAAM,KAAA,MAClE,CAAC,MAChB,OAAO;CAER,MAAM,IAAI,YAAY,IAAI,KAAK,CAAC;CAIhC,MAAM,gBAAgB,QAAQ,IAAI,IAAI;CAEtC,MAAM,WAAW,MAAM,KAAK,SAAS,GAAG;CACxC,IAAI,YAAY,MACf,IAAI,QAAQ,wBAAwB;CAGrC,IAAI;CACJ,IAAI,KAAK,cAAc,OACtB,iBAAiB;MACX,IAAI,YAAY,MAAM;EAG5B,iBAAiB;EACjB,yBAAyB,GAAG;CAC7B,OAAO;EACN,MAAM,UAAU,KAAK,QAAQ,OAAO,OAAO,KAAK,IAAI,IAAI;EACxD,MAAM,EAAE,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM;GACjD,KAAK,eAAe,QAAQ,GAAG;GAC/B,KAAK,KAAK,UAAU;GACpB,QAAQ,KAAK,UAAU;GACvB;EACD,CAAC;EACD,IAAI,CAAC,IACJ,MAAM,IAAI,SAAS,EAAE,KAAK,eAAe,GAAG,GAAG;EAEhD,iBAAiB;CAClB;CAKA,MAAM,cAAc,KAAK,aAAa,QAAQ,OAAO;CAErD,MAAM,EAAE,SAAS,UAAU,iBAAiB,sBAD5B,KAAK,UAA4C,CAAC,GACQ,WAAW;CACrF,KAAK,SAAS;CAEd,IAAI,CAAC,iBAAiB,gBAAgB,QAAQ,kBAAkB,QAAQ,GACvE,MAAM,IAAI,SAAS,EAAE,KAAK,YAAY,GAAG,GAAG;CAG7C,MAAM,iBAAiB,QAAQ,KAAK,OAAO,KAAK,CAAC;CACjD,IAAI,KAAK,WAAW,CAAC;MAKhB,EAHH,OAAO,iBAAiB,YAAY,aAAa,SAAS,IACvD,MAAM,KAAK,QAAQ,OAAO;GAAE,OAAO;GAAc;EAAI,CAAC,EAAE,YAAY,KAAK,IACzE,QAEH,MAAM,IAAI,SAAS,EAAE,KAAK,iBAAiB,GAAG,GAAG;CAAA;CAInD,MAAM,aAAsC;EAC3C,qBAAI,IAAI,KAAK,GAAE,YAAY;EAC3B,MAAM;GAAE,SAAS,iBAAiB,WAAW;GAAW,WAAW;EAAe;CACnF;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,SAAS,IAAI,SAAS,KAAK,QAAQ;EAC9C,IAAI,IACH,WAAW,KAAK;CAElB;CACA,IAAI,KAAK,SAAS,IAAI;EACrB,MAAM,KAAK,IAAI,SAAS,IAAI,YAAY;EACxC,IAAI,IACH,WAAW,KAAK;CAElB;CAOA,KAAK,OAAO;EAAE,GAHb,iBAAiB,KAAK,QAAQ,QAAQ,OAAO,KAAK,SAAS,WACvD,KAAK,OACN,CAAC;EACwB,GAAG;CAAW;CAE3C,OAAO;AACR"}
@@ -22,7 +22,7 @@ type CaptchaVerifyArgs = {
22
22
  token: string;
23
23
  req: PayloadRequest;
24
24
  };
25
- /** A captcha adapter. v1 ships the seam only (no built-in provider). Build with `defineCaptchaProvider`. */
25
+ /** A captcha adapter. Use a bundled provider (Turnstile/reCAPTCHA/hCaptcha) or build one with `defineCaptchaProvider`. */
26
26
  type CaptchaProvider = {
27
27
  type: string;
28
28
  verify(args: CaptchaVerifyArgs): Promise<boolean>;
@@ -37,13 +37,27 @@ type SpamMetadataConfig = {
37
37
  ua?: boolean;
38
38
  };
39
39
  type SpamConfig = {
40
- /** Honeypot decoy. Default on. `false` disables. */honeypot?: false | {
40
+ /**
41
+ * Honeypot decoy. Default on. `false` disables. `fieldName` is accepted for backward compatibility
42
+ * but no longer participates in server matching: the decoy is submitted under a fixed reserved key,
43
+ * so it never collides with a real field, and its DOM input name is set on the client via `<Form honeypot>`.
44
+ */
45
+ honeypot?: false | {
41
46
  fieldName?: string;
42
47
  }; /** Per-identity rate limit on submission create. Default on (60s window, max 5). `false` disables. */
43
48
  rateLimit?: false | RateLimitConfig; /** Per-identity rate limit on upload create. Default on (60s window, max 20). `false` disables. */
44
- uploadRateLimit?: false | RateLimitConfig; /** A captcha provider (none by default; seam only in v1). */
49
+ uploadRateLimit?: false | RateLimitConfig; /** A captcha provider (none by default): `turnstileProvider`/`recaptchaProvider`/`hcaptchaProvider` or a custom adapter. */
45
50
  captcha?: CaptchaProvider; /** Identity resolution for rate-limiting, upload ownership, and (future) poll dedup. Default: user id, else trusted IP header. */
46
- identify?: IdentifyFn; /** Header read for the client IP (proxy-dependent, best-effort). Default 'x-forwarded-for'. */
51
+ identify?: IdentifyFn;
52
+ /**
53
+ * How strictly a submitted file's upload-ownership is enforced. `'lenient'` (default) only rejects a
54
+ * stamped upload when the submitter is identified and does not match, and passes an owned upload
55
+ * through when the submitter cannot be identified (fail-open, consistent with rate-limiting).
56
+ * `'strict'` also rejects a stamped upload whenever the submitter cannot be identified, so a
57
+ * deployment that always identifies requests (a proxy setting the trusted IP header, or a custom
58
+ * `identify`) never lets an unverifiable claim capture an owned upload.
59
+ */
60
+ uploadOwnership?: 'lenient' | 'strict'; /** Header read for the client IP (proxy-dependent, best-effort). Default 'x-forwarded-for'. */
47
61
  ipHeader?: string; /** Opt-in capture of client metadata onto the submission `meta`. Off by default. */
48
62
  metadata?: SpamMetadataConfig;
49
63
  };
@@ -67,6 +81,7 @@ type ResolvedSpamConfig = {
67
81
  ip: boolean;
68
82
  ua: boolean;
69
83
  };
84
+ uploadOwnership: 'lenient' | 'strict';
70
85
  };
71
86
  //#endregion
72
87
  export { CaptchaProvider, CaptchaVerifyArgs, IdentifyFn, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, ResolvedSpamConfig, SpamConfig, SpamMetadataConfig, SpamOption };
@@ -82,14 +82,18 @@ const SubmissionAnswers = ({ data, req }) => {
82
82
  field: descriptor.field,
83
83
  label: descriptor.label,
84
84
  value: formatted || "—",
85
- multiline: descriptor.fieldType === "textarea"
85
+ multiline: descriptor.fieldType === "textarea",
86
+ ...descriptor.width ? { width: descriptor.width } : {}
86
87
  });
87
88
  }
88
89
  const consent = rawConsent.map((entry) => ({
89
90
  ...entry,
90
91
  at: formatDate(entry.at, locale)
91
92
  }));
92
- const metaItems = [];
93
+ const metaItems = [{
94
+ label: t(keys.submissionMetaLocale),
95
+ value: locale
96
+ }];
93
97
  if (meta) {
94
98
  if (meta.at) metaItems.push({
95
99
  label: t(keys.submissionMetaReceivedAt),
@@ -1 +1 @@
1
- {"version":3,"file":"SubmissionAnswers.js","names":[],"sources":["../../src/submissions/SubmissionAnswers.tsx"],"sourcesContent":["import type { UIFieldServerProps } from 'payload'\nimport { defaultFieldDefinitions } from '../fields/builtin'\nimport { buildRegistry } from '../fields/registry'\nimport { keys } from '../translations/keys'\nimport { asFieldTranslate } from '../translations/server'\nimport type {\n\tAnswerItem,\n\tConsentItem,\n\tMetaItem,\n\tRepeaterItem,\n\tSubmissionAnswersLabels,\n} from './SubmissionAnswersClient'\nimport { SubmissionAnswersClient } from './SubmissionAnswersClient'\nimport type { SubmissionDescriptor, SubmissionValue } from './types'\n\nconst registry = buildRegistry(defaultFieldDefinitions)\n\ntype ConsentEntry = {\n\tfield: string\n\tagreed: boolean\n\tref?: string\n\tversionRef?: string\n\tat: string\n}\n\ntype MetaSpam = { captcha?: string }\n\ntype SubmissionMeta = {\n\tat?: string\n\tip?: string\n\tua?: string\n\tspam?: MetaSpam\n\t[key: string]: unknown\n}\n\ntype SubmissionDoc = {\n\tvalues?: SubmissionValue[]\n\tdescriptors?: SubmissionDescriptor[]\n\tlocale?: string\n\tconsent?: ConsentEntry[]\n\tmeta?: SubmissionMeta\n}\n\nconst formatDate = (iso: string, locale: string): string => {\n\ttry {\n\t\treturn new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(\n\t\t\tnew Date(iso)\n\t\t)\n\t} catch {\n\t\treturn iso\n\t}\n}\n\n/**\n * Server component. Formats submission data into serializable props and delegates\n * rendering to the client component which uses Payload's UI components.\n */\nexport const SubmissionAnswers = ({ data, req }: UIFieldServerProps) => {\n\tconst doc = (data ?? {}) as SubmissionDoc\n\tconst descriptors = doc.descriptors ?? []\n\tconst values = doc.values ?? []\n\tconst locale = doc.locale ?? req.locale ?? 'en'\n\tconst t = asFieldTranslate(req.i18n.t)\n\tconst rawConsent = doc.consent ?? []\n\tconst meta = doc.meta\n\n\tconst valueByField = new Map(values.map((entry) => [entry.field, entry.value]))\n\n\tconst answers: AnswerItem[] = []\n\tconst repeaters: RepeaterItem[] = []\n\n\tfor (const descriptor of descriptors) {\n\t\tconst raw = valueByField.get(descriptor.field)\n\n\t\tif (descriptor.fieldType === 'repeater') {\n\t\t\tconst rows = Array.isArray(raw) ? (raw as Array<Record<string, unknown>>) : []\n\t\t\tconst subDescs = descriptor.subFieldDescriptors ?? []\n\t\t\trepeaters.push({\n\t\t\t\tfield: descriptor.field,\n\t\t\t\tlabel: descriptor.label,\n\t\t\t\trows: rows.map((row, rowIndex) => ({\n\t\t\t\t\tid: String(rowIndex),\n\t\t\t\t\tsubFields: subDescs.map((sub) => {\n\t\t\t\t\t\tconst subDef = registry.get(sub.fieldType)\n\t\t\t\t\t\tconst subRaw = row[sub.field]\n\t\t\t\t\t\tconst formatted = subDef?.format\n\t\t\t\t\t\t\t? subDef.format({\n\t\t\t\t\t\t\t\t\tvalue: subRaw,\n\t\t\t\t\t\t\t\t\tconfig: {},\n\t\t\t\t\t\t\t\t\toptionLabels: sub.optionLabels,\n\t\t\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\t\t\tt,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t: subRaw == null\n\t\t\t\t\t\t\t\t? ''\n\t\t\t\t\t\t\t\t: String(subRaw)\n\t\t\t\t\t\treturn { label: sub.label, value: formatted || '—' }\n\t\t\t\t\t}),\n\t\t\t\t})),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tconst def = registry.get(descriptor.fieldType)\n\t\tconst formatted = def?.format\n\t\t\t? def.format({ value: raw, config: {}, optionLabels: descriptor.optionLabels, locale, t })\n\t\t\t: raw == null\n\t\t\t\t? ''\n\t\t\t\t: String(raw)\n\n\t\tif (descriptor.fieldType === 'file') {\n\t\t\tconst ref =\n\t\t\t\traw && typeof raw === 'object' && 'url' in raw\n\t\t\t\t\t? (raw as { url?: string; filename?: string })\n\t\t\t\t\t: null\n\t\t\tconst url = typeof ref?.url === 'string' ? ref.url : undefined\n\t\t\tconst filename = typeof ref?.filename === 'string' ? ref.filename : (url ?? '')\n\t\t\tanswers.push({ field: descriptor.field, label: descriptor.label, value: filename, href: url })\n\t\t} else {\n\t\t\tanswers.push({\n\t\t\t\tfield: descriptor.field,\n\t\t\t\tlabel: descriptor.label,\n\t\t\t\tvalue: formatted || '—',\n\t\t\t\tmultiline: descriptor.fieldType === 'textarea',\n\t\t\t})\n\t\t}\n\t}\n\n\tconst consent: ConsentItem[] = rawConsent.map((entry) => ({\n\t\t...entry,\n\t\tat: formatDate(entry.at, locale),\n\t}))\n\n\tconst metaItems: MetaItem[] = []\n\tif (meta) {\n\t\tif (meta.at)\n\t\t\tmetaItems.push({\n\t\t\t\tlabel: t(keys.submissionMetaReceivedAt),\n\t\t\t\tvalue: formatDate(meta.at, locale),\n\t\t\t})\n\t\tif (meta.ip) metaItems.push({ label: t(keys.submissionMetaIp), value: meta.ip })\n\t\tif (meta.ua) metaItems.push({ label: t(keys.submissionMetaUserAgent), value: String(meta.ua) })\n\t\tif (meta.spam) {\n\t\t\tconst captcha =\n\t\t\t\ttypeof meta.spam === 'object' && 'captcha' in meta.spam\n\t\t\t\t\t? String((meta.spam as MetaSpam).captcha)\n\t\t\t\t\t: '—'\n\t\t\tmetaItems.push({ label: t(keys.submissionMetaCaptcha), value: captcha })\n\t\t}\n\t}\n\n\tconst labels: SubmissionAnswersLabels = {\n\t\tanswers: t(keys.submissionAnswers),\n\t\tconsent: t(keys.submissionConsent),\n\t\tsubmissionDetails: t(keys.submissionDetails),\n\t\tagreed: t(keys.submissionConsentAgreed),\n\t\tdeclined: t(keys.submissionConsentDeclined),\n\t\trow: t(keys.repeaterRow),\n\t\tempty: t(keys.submissionNoAnswers),\n\t}\n\n\treturn (\n\t\t<SubmissionAnswersClient\n\t\t\tanswers={answers}\n\t\t\trepeaters={repeaters}\n\t\t\tconsent={consent}\n\t\t\tmeta={metaItems}\n\t\t\tlabels={labels}\n\t\t/>\n\t)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SubmissionAnswers.js","names":[],"sources":["../../src/submissions/SubmissionAnswers.tsx"],"sourcesContent":["import type { UIFieldServerProps } from 'payload'\nimport { defaultFieldDefinitions } from '../fields/builtin'\nimport { buildRegistry } from '../fields/registry'\nimport { keys } from '../translations/keys'\nimport { asFieldTranslate } from '../translations/server'\nimport type {\n\tAnswerItem,\n\tConsentItem,\n\tMetaItem,\n\tRepeaterItem,\n\tSubmissionAnswersLabels,\n} from './SubmissionAnswersClient'\nimport { SubmissionAnswersClient } from './SubmissionAnswersClient'\nimport type { SubmissionDescriptor, SubmissionValue } from './types'\n\nconst registry = buildRegistry(defaultFieldDefinitions)\n\ntype ConsentEntry = {\n\tfield: string\n\tagreed: boolean\n\t/** The consent source's name at submit time (see `ConsentProof.name`). */\n\tname?: string\n\t/** The plain-text snapshot of the wording agreed to (see `ConsentProof.statementText`). */\n\tstatementText?: string\n\tversionRef?: string\n\tat: string\n}\n\ntype MetaSpam = { captcha?: string }\n\ntype SubmissionMeta = {\n\tat?: string\n\tip?: string\n\tua?: string\n\tspam?: MetaSpam\n\t[key: string]: unknown\n}\n\ntype SubmissionDoc = {\n\tvalues?: SubmissionValue[]\n\tdescriptors?: SubmissionDescriptor[]\n\tlocale?: string\n\tconsent?: ConsentEntry[]\n\tmeta?: SubmissionMeta\n}\n\nconst formatDate = (iso: string, locale: string): string => {\n\ttry {\n\t\treturn new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(\n\t\t\tnew Date(iso)\n\t\t)\n\t} catch {\n\t\treturn iso\n\t}\n}\n\n/**\n * Server component. Formats submission data into serializable props and delegates\n * rendering to the client component which uses Payload's UI components.\n */\nexport const SubmissionAnswers = ({ data, req }: UIFieldServerProps) => {\n\tconst doc = (data ?? {}) as SubmissionDoc\n\tconst descriptors = doc.descriptors ?? []\n\tconst values = doc.values ?? []\n\tconst locale = doc.locale ?? req.locale ?? 'en'\n\tconst t = asFieldTranslate(req.i18n.t)\n\tconst rawConsent = doc.consent ?? []\n\tconst meta = doc.meta\n\n\tconst valueByField = new Map(values.map((entry) => [entry.field, entry.value]))\n\n\tconst answers: AnswerItem[] = []\n\tconst repeaters: RepeaterItem[] = []\n\n\tfor (const descriptor of descriptors) {\n\t\tconst raw = valueByField.get(descriptor.field)\n\n\t\tif (descriptor.fieldType === 'repeater') {\n\t\t\tconst rows = Array.isArray(raw) ? (raw as Array<Record<string, unknown>>) : []\n\t\t\tconst subDescs = descriptor.subFieldDescriptors ?? []\n\t\t\trepeaters.push({\n\t\t\t\tfield: descriptor.field,\n\t\t\t\tlabel: descriptor.label,\n\t\t\t\trows: rows.map((row, rowIndex) => ({\n\t\t\t\t\tid: String(rowIndex),\n\t\t\t\t\tsubFields: subDescs.map((sub) => {\n\t\t\t\t\t\tconst subDef = registry.get(sub.fieldType)\n\t\t\t\t\t\tconst subRaw = row[sub.field]\n\t\t\t\t\t\tconst formatted = subDef?.format\n\t\t\t\t\t\t\t? subDef.format({\n\t\t\t\t\t\t\t\t\tvalue: subRaw,\n\t\t\t\t\t\t\t\t\tconfig: {},\n\t\t\t\t\t\t\t\t\toptionLabels: sub.optionLabels,\n\t\t\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\t\t\tt,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t: subRaw == null\n\t\t\t\t\t\t\t\t? ''\n\t\t\t\t\t\t\t\t: String(subRaw)\n\t\t\t\t\t\treturn { label: sub.label, value: formatted || '—' }\n\t\t\t\t\t}),\n\t\t\t\t})),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tconst def = registry.get(descriptor.fieldType)\n\t\tconst formatted = def?.format\n\t\t\t? def.format({ value: raw, config: {}, optionLabels: descriptor.optionLabels, locale, t })\n\t\t\t: raw == null\n\t\t\t\t? ''\n\t\t\t\t: String(raw)\n\n\t\tif (descriptor.fieldType === 'file') {\n\t\t\tconst ref =\n\t\t\t\traw && typeof raw === 'object' && 'url' in raw\n\t\t\t\t\t? (raw as { url?: string; filename?: string })\n\t\t\t\t\t: null\n\t\t\tconst url = typeof ref?.url === 'string' ? ref.url : undefined\n\t\t\tconst filename = typeof ref?.filename === 'string' ? ref.filename : (url ?? '')\n\t\t\tanswers.push({ field: descriptor.field, label: descriptor.label, value: filename, href: url })\n\t\t} else {\n\t\t\tanswers.push({\n\t\t\t\tfield: descriptor.field,\n\t\t\t\tlabel: descriptor.label,\n\t\t\t\tvalue: formatted || '—',\n\t\t\t\tmultiline: descriptor.fieldType === 'textarea',\n\t\t\t\t...(descriptor.width ? { width: descriptor.width } : {}),\n\t\t\t})\n\t\t}\n\t}\n\n\tconst consent: ConsentItem[] = rawConsent.map((entry) => ({\n\t\t...entry,\n\t\tat: formatDate(entry.at, locale),\n\t}))\n\n\tconst metaItems: MetaItem[] = [{ label: t(keys.submissionMetaLocale), value: locale }]\n\tif (meta) {\n\t\tif (meta.at)\n\t\t\tmetaItems.push({\n\t\t\t\tlabel: t(keys.submissionMetaReceivedAt),\n\t\t\t\tvalue: formatDate(meta.at, locale),\n\t\t\t})\n\t\tif (meta.ip) metaItems.push({ label: t(keys.submissionMetaIp), value: meta.ip })\n\t\tif (meta.ua) metaItems.push({ label: t(keys.submissionMetaUserAgent), value: String(meta.ua) })\n\t\tif (meta.spam) {\n\t\t\tconst captcha =\n\t\t\t\ttypeof meta.spam === 'object' && 'captcha' in meta.spam\n\t\t\t\t\t? String((meta.spam as MetaSpam).captcha)\n\t\t\t\t\t: '—'\n\t\t\tmetaItems.push({ label: t(keys.submissionMetaCaptcha), value: captcha })\n\t\t}\n\t}\n\n\tconst labels: SubmissionAnswersLabels = {\n\t\tanswers: t(keys.submissionAnswers),\n\t\tconsent: t(keys.submissionConsent),\n\t\tsubmissionDetails: t(keys.submissionDetails),\n\t\tagreed: t(keys.submissionConsentAgreed),\n\t\tdeclined: t(keys.submissionConsentDeclined),\n\t\trow: t(keys.repeaterRow),\n\t\tempty: t(keys.submissionNoAnswers),\n\t}\n\n\treturn (\n\t\t<SubmissionAnswersClient\n\t\t\tanswers={answers}\n\t\t\trepeaters={repeaters}\n\t\t\tconsent={consent}\n\t\t\tmeta={metaItems}\n\t\t\tlabels={labels}\n\t\t/>\n\t)\n}\n"],"mappings":"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"}