@teleporthq/teleport-plugin-next-workflows 0.44.11 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (976) hide show
  1. package/__tests__/_helpers/fake-fetch.ts +75 -0
  2. package/__tests__/_helpers/gui-place-order-statements.ts +17 -0
  3. package/__tests__/_helpers/inserted-lines.ts +22 -0
  4. package/__tests__/_helpers/invoice-store.ts +10 -1
  5. package/__tests__/_helpers/load-security-runtime.ts +45 -0
  6. package/__tests__/_helpers/load-server-runtime.ts +26 -3
  7. package/__tests__/_helpers/run-account-delete-route.ts +3 -1
  8. package/__tests__/_helpers/run-generated-middleware.ts +7 -2
  9. package/__tests__/account-delete-owned-rows.test.ts +3 -1
  10. package/__tests__/adopted-element-trigger-code.test.ts +128 -0
  11. package/__tests__/analytics-track-outcome.test.ts +145 -0
  12. package/__tests__/auth-password-change-ends-sessions.test.ts +8 -1
  13. package/__tests__/auth-password-security.test.ts +2275 -0
  14. package/__tests__/auth-session-cost.test.ts +31 -0
  15. package/__tests__/auth-signup-route.test.ts +960 -0
  16. package/__tests__/authored-code-every-path.test.ts +247 -0
  17. package/__tests__/automations-bus.test.ts +3820 -0
  18. package/__tests__/automations-catalog-drift.test.ts +97 -0
  19. package/__tests__/booking-calendar-feed-route.test.ts +20 -3
  20. package/__tests__/cart-price-order.test.ts +336 -1
  21. package/__tests__/code-lookup-rate-limit-route.test.ts +189 -0
  22. package/__tests__/cost-bearing-node-types.test.ts +119 -0
  23. package/__tests__/credential-columns-reply.test.ts +9 -0
  24. package/__tests__/credential-result-reply.test.ts +19 -5
  25. package/__tests__/cron-route-loop-templateparams.test.ts +6 -3
  26. package/__tests__/custom-js-authored-code.test.ts +304 -0
  27. package/__tests__/custom-node-custom-code-gate.test.ts +136 -0
  28. package/__tests__/custom-node-step-handlers.test.ts +251 -0
  29. package/__tests__/custom-node-step-reach.test.ts +63 -0
  30. package/__tests__/data-api-anon-user-id-fallback.test.ts +7 -2
  31. package/__tests__/data-api-browser-reads.test.ts +7 -0
  32. package/__tests__/data-api-business-events.test.ts +403 -0
  33. package/__tests__/data-api-invalid-body.test.ts +7 -0
  34. package/__tests__/data-api-low-stock-autofire.test.ts +30 -4
  35. package/__tests__/data-api-optional-feature-columns.test.ts +44 -0
  36. package/__tests__/data-api-owner-scope-credentials.test.ts +1324 -0
  37. package/__tests__/data-api-protected-tables.test.ts +48 -15
  38. package/__tests__/data-api-retired-table-and-account-fields.test.ts +208 -0
  39. package/__tests__/email-locale-senders.test.ts +5 -3
  40. package/__tests__/expression-injection.test.ts +343 -0
  41. package/__tests__/fixtures/admin-file-workflows.json +242 -0
  42. package/__tests__/form-confirmation-email-template.test.ts +44 -0
  43. package/__tests__/internal-request-headers.test.ts +25 -0
  44. package/__tests__/invoice-pdf-service-project-header.test.ts +120 -0
  45. package/__tests__/invoice-self-call-origin.test.ts +57 -1
  46. package/__tests__/legacy-webhook-self-calls.test.ts +86 -1
  47. package/__tests__/middleware-home-route-protection.test.ts +3 -1
  48. package/__tests__/middleware-subscriber-access.test.ts +1 -1
  49. package/__tests__/newsletter-admin-route.test.ts +649 -0
  50. package/__tests__/newsletter-plugin.test.ts +1248 -0
  51. package/__tests__/outbound-nodes-security.test.ts +1929 -0
  52. package/__tests__/page-access-guard.test.ts +217 -0
  53. package/__tests__/page-access-route-table.test.ts +231 -0
  54. package/__tests__/payment-drivers-module.test.ts +23 -0
  55. package/__tests__/payment-legacy-webhook-routes.test.ts +100 -5
  56. package/__tests__/payment-webhook-route.test.ts +4 -1
  57. package/__tests__/paypal-webhook-signature-verification.test.ts +44 -0
  58. package/__tests__/place-order-guards.test.ts +506 -0
  59. package/__tests__/public-step-guards.test.ts +310 -0
  60. package/__tests__/publish-refusals.test.ts +505 -0
  61. package/__tests__/realtime-identity-routes.test.ts +492 -0
  62. package/__tests__/record-event-writer-server-only.test.ts +975 -0
  63. package/__tests__/resolve-config-template-params.test.ts +3 -3
  64. package/__tests__/runtime-storage-upload-access.test.ts +280 -17
  65. package/__tests__/security-integration-env-names.test.ts +74 -0
  66. package/__tests__/security-lists-parity.test.ts +245 -0
  67. package/__tests__/security-runtime-shadow.test.ts +224 -1
  68. package/__tests__/security-runtime.test.ts +893 -0
  69. package/__tests__/segment-rate-limit-error-branch.test.ts +284 -0
  70. package/__tests__/segment-route-abuse-gate.test.ts +309 -0
  71. package/__tests__/server-job-hops-real-data-api.test.ts +669 -0
  72. package/__tests__/server-session-identity.test.ts +522 -0
  73. package/__tests__/server-step-chain.test.ts +222 -0
  74. package/__tests__/server-step-signing.test.ts +107 -2
  75. package/__tests__/session-cookie-resolver.test.ts +62 -106
  76. package/__tests__/signup-event.test.ts +649 -0
  77. package/__tests__/signup-reply-identical.test.ts +144 -0
  78. package/__tests__/slack-message-escaping.test.ts +123 -0
  79. package/__tests__/visitor-step-limits.test.ts +318 -0
  80. package/__tests__/webhook-delivery-lease.test.ts +4 -1
  81. package/__tests__/webpack-safe-handlers.test.ts +57 -8
  82. package/__tests__/workflow-auth-guard.test.ts +9 -1
  83. package/dist/cjs/account-delete-route-generator.d.ts +1 -0
  84. package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
  85. package/dist/cjs/account-delete-route-generator.js +18 -3
  86. package/dist/cjs/account-delete-route-generator.js.map +1 -1
  87. package/dist/cjs/api-route-generator.d.ts +52 -3
  88. package/dist/cjs/api-route-generator.d.ts.map +1 -1
  89. package/dist/cjs/api-route-generator.js +600 -145
  90. package/dist/cjs/api-route-generator.js.map +1 -1
  91. package/dist/cjs/auth-generator.d.ts +126 -3
  92. package/dist/cjs/auth-generator.d.ts.map +1 -1
  93. package/dist/cjs/auth-generator.js +1353 -139
  94. package/dist/cjs/auth-generator.js.map +1 -1
  95. package/dist/cjs/automations/activity-kicks.d.ts +12 -0
  96. package/dist/cjs/automations/activity-kicks.d.ts.map +1 -0
  97. package/dist/cjs/automations/activity-kicks.js +123 -0
  98. package/dist/cjs/automations/activity-kicks.js.map +1 -0
  99. package/dist/cjs/automations/app-events-source.d.ts +33 -0
  100. package/dist/cjs/automations/app-events-source.d.ts.map +1 -0
  101. package/dist/cjs/automations/app-events-source.js +109 -0
  102. package/dist/cjs/automations/app-events-source.js.map +1 -0
  103. package/dist/cjs/automations/automations-project-plugin.d.ts +21 -0
  104. package/dist/cjs/automations/automations-project-plugin.d.ts.map +1 -0
  105. package/dist/cjs/automations/automations-project-plugin.js +102 -0
  106. package/dist/cjs/automations/automations-project-plugin.js.map +1 -0
  107. package/dist/cjs/automations/automations-scope.d.ts +104 -0
  108. package/dist/cjs/automations/automations-scope.d.ts.map +1 -0
  109. package/dist/cjs/automations/automations-scope.js +176 -0
  110. package/dist/cjs/automations/automations-scope.js.map +1 -0
  111. package/dist/cjs/automations/dispatcher-source.d.ts +72 -0
  112. package/dist/cjs/automations/dispatcher-source.d.ts.map +1 -0
  113. package/dist/cjs/automations/dispatcher-source.js +1008 -0
  114. package/dist/cjs/automations/dispatcher-source.js.map +1 -0
  115. package/dist/cjs/automations/event-bus-source.d.ts +44 -0
  116. package/dist/cjs/automations/event-bus-source.d.ts.map +1 -0
  117. package/dist/cjs/automations/event-bus-source.js +427 -0
  118. package/dist/cjs/automations/event-bus-source.js.map +1 -0
  119. package/dist/cjs/automations/handler-module-generator.d.ts +25 -0
  120. package/dist/cjs/automations/handler-module-generator.d.ts.map +1 -0
  121. package/dist/cjs/automations/handler-module-generator.js +78 -0
  122. package/dist/cjs/automations/handler-module-generator.js.map +1 -0
  123. package/dist/cjs/automations/hydrators-source.d.ts +33 -0
  124. package/dist/cjs/automations/hydrators-source.d.ts.map +1 -0
  125. package/dist/cjs/automations/hydrators-source.js +571 -0
  126. package/dist/cjs/automations/hydrators-source.js.map +1 -0
  127. package/dist/cjs/booking-calendar-feed-route-generator.d.ts.map +1 -1
  128. package/dist/cjs/booking-calendar-feed-route-generator.js +8 -0
  129. package/dist/cjs/booking-calendar-feed-route-generator.js.map +1 -1
  130. package/dist/cjs/configured-nextauth-url.d.ts +17 -0
  131. package/dist/cjs/configured-nextauth-url.d.ts.map +1 -0
  132. package/dist/cjs/configured-nextauth-url.js +20 -0
  133. package/dist/cjs/configured-nextauth-url.js.map +1 -0
  134. package/dist/cjs/cron-request-verification.d.ts +22 -1
  135. package/dist/cjs/cron-request-verification.d.ts.map +1 -1
  136. package/dist/cjs/cron-request-verification.js +50 -6
  137. package/dist/cjs/cron-request-verification.js.map +1 -1
  138. package/dist/cjs/data-api-route-generator.d.ts +19 -0
  139. package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
  140. package/dist/cjs/data-api-route-generator.js +711 -43
  141. package/dist/cjs/data-api-route-generator.js.map +1 -1
  142. package/dist/cjs/ecommerce/parameterized-stock-chain.d.ts +3 -1
  143. package/dist/cjs/ecommerce/parameterized-stock-chain.d.ts.map +1 -1
  144. package/dist/cjs/ecommerce/parameterized-stock-chain.js +45 -5
  145. package/dist/cjs/ecommerce/parameterized-stock-chain.js.map +1 -1
  146. package/dist/cjs/ecommerce/place-order-guards.d.ts +126 -0
  147. package/dist/cjs/ecommerce/place-order-guards.d.ts.map +1 -0
  148. package/dist/cjs/ecommerce/place-order-guards.js +670 -0
  149. package/dist/cjs/ecommerce/place-order-guards.js.map +1 -0
  150. package/dist/cjs/ecommerce-customhandler-rewriter.d.ts +2 -0
  151. package/dist/cjs/ecommerce-customhandler-rewriter.d.ts.map +1 -1
  152. package/dist/cjs/ecommerce-customhandler-rewriter.js +38 -2
  153. package/dist/cjs/ecommerce-customhandler-rewriter.js.map +1 -1
  154. package/dist/cjs/executor-generator.d.ts.map +1 -1
  155. package/dist/cjs/executor-generator.js +368 -60
  156. package/dist/cjs/executor-generator.js.map +1 -1
  157. package/dist/cjs/graph-utils.d.ts +9 -0
  158. package/dist/cjs/graph-utils.d.ts.map +1 -1
  159. package/dist/cjs/graph-utils.js +32 -1
  160. package/dist/cjs/graph-utils.js.map +1 -1
  161. package/dist/cjs/index.d.ts +20 -1
  162. package/dist/cjs/index.d.ts.map +1 -1
  163. package/dist/cjs/index.js +72 -2
  164. package/dist/cjs/index.js.map +1 -1
  165. package/dist/cjs/invoice/api-routes-code.d.ts +9 -3
  166. package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
  167. package/dist/cjs/invoice/api-routes-code.js +9 -7
  168. package/dist/cjs/invoice/api-routes-code.js.map +1 -1
  169. package/dist/cjs/invoice/index.d.ts.map +1 -1
  170. package/dist/cjs/invoice/index.js +12 -7
  171. package/dist/cjs/invoice/index.js.map +1 -1
  172. package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
  173. package/dist/cjs/invoice/invoice-assembly-code.js +8 -3
  174. package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
  175. package/dist/cjs/invoice/pdf-generator-code.d.ts.map +1 -1
  176. package/dist/cjs/invoice/pdf-generator-code.js +2 -1
  177. package/dist/cjs/invoice/pdf-generator-code.js.map +1 -1
  178. package/dist/cjs/invoice/pdf-service-client-code.d.ts.map +1 -1
  179. package/dist/cjs/invoice/pdf-service-client-code.js +15 -7
  180. package/dist/cjs/invoice/pdf-service-client-code.js.map +1 -1
  181. package/dist/cjs/newsletter/admin-route-code.d.ts +60 -0
  182. package/dist/cjs/newsletter/admin-route-code.d.ts.map +1 -0
  183. package/dist/cjs/newsletter/admin-route-code.js +300 -0
  184. package/dist/cjs/newsletter/admin-route-code.js.map +1 -0
  185. package/dist/cjs/newsletter/helper-code.d.ts +109 -0
  186. package/dist/cjs/newsletter/helper-code.d.ts.map +1 -0
  187. package/dist/cjs/newsletter/helper-code.js +662 -0
  188. package/dist/cjs/newsletter/helper-code.js.map +1 -0
  189. package/dist/cjs/newsletter/newsletter-scope.d.ts +65 -0
  190. package/dist/cjs/newsletter/newsletter-scope.d.ts.map +1 -0
  191. package/dist/cjs/newsletter/newsletter-scope.js +103 -0
  192. package/dist/cjs/newsletter/newsletter-scope.js.map +1 -0
  193. package/dist/cjs/newsletter/project-plugin.d.ts +18 -0
  194. package/dist/cjs/newsletter/project-plugin.d.ts.map +1 -0
  195. package/dist/cjs/newsletter/project-plugin.js +97 -0
  196. package/dist/cjs/newsletter/project-plugin.js.map +1 -0
  197. package/dist/cjs/newsletter/routes-code.d.ts +6 -0
  198. package/dist/cjs/newsletter/routes-code.d.ts.map +1 -0
  199. package/dist/cjs/newsletter/routes-code.js +181 -0
  200. package/dist/cjs/newsletter/routes-code.js.map +1 -0
  201. package/dist/cjs/nodes/account/account-compare-passwords.d.ts.map +1 -1
  202. package/dist/cjs/nodes/account/account-compare-passwords.js +16 -6
  203. package/dist/cjs/nodes/account/account-compare-passwords.js.map +1 -1
  204. package/dist/cjs/nodes/account/account-delete-current.js +1 -1
  205. package/dist/cjs/nodes/account/account-get-current.js +1 -1
  206. package/dist/cjs/nodes/account/account-hash-password.d.ts.map +1 -1
  207. package/dist/cjs/nodes/account/account-hash-password.js +15 -3
  208. package/dist/cjs/nodes/account/account-hash-password.js.map +1 -1
  209. package/dist/cjs/nodes/account/account-login.d.ts.map +1 -1
  210. package/dist/cjs/nodes/account/account-login.js +23 -6
  211. package/dist/cjs/nodes/account/account-login.js.map +1 -1
  212. package/dist/cjs/nodes/account/account-logout.js +1 -1
  213. package/dist/cjs/nodes/account/account-refresh-session.js +1 -1
  214. package/dist/cjs/nodes/account/account-request-password-reset.d.ts +3 -0
  215. package/dist/cjs/nodes/account/account-request-password-reset.d.ts.map +1 -0
  216. package/dist/cjs/nodes/account/account-request-password-reset.js +46 -0
  217. package/dist/cjs/nodes/account/account-request-password-reset.js.map +1 -0
  218. package/dist/cjs/nodes/account/account-reset-password.d.ts +3 -0
  219. package/dist/cjs/nodes/account/account-reset-password.d.ts.map +1 -0
  220. package/dist/cjs/nodes/account/account-reset-password.js +44 -0
  221. package/dist/cjs/nodes/account/account-reset-password.js.map +1 -0
  222. package/dist/cjs/nodes/account/account-signup.d.ts.map +1 -1
  223. package/dist/cjs/nodes/account/account-signup.js +82 -19
  224. package/dist/cjs/nodes/account/account-signup.js.map +1 -1
  225. package/dist/cjs/nodes/account/account-social-login.js +1 -1
  226. package/dist/cjs/nodes/ai/ai-custom-prompt.d.ts.map +1 -1
  227. package/dist/cjs/nodes/ai/ai-custom-prompt.js +20 -5
  228. package/dist/cjs/nodes/ai/ai-custom-prompt.js.map +1 -1
  229. package/dist/cjs/nodes/ai/ai-detect-language.d.ts.map +1 -1
  230. package/dist/cjs/nodes/ai/ai-detect-language.js +20 -5
  231. package/dist/cjs/nodes/ai/ai-detect-language.js.map +1 -1
  232. package/dist/cjs/nodes/ai/ai-generate-text-embedding.d.ts.map +1 -1
  233. package/dist/cjs/nodes/ai/ai-generate-text-embedding.js +10 -2
  234. package/dist/cjs/nodes/ai/ai-generate-text-embedding.js.map +1 -1
  235. package/dist/cjs/nodes/ai/ai-provider-utils-sources.generated.js +2 -2
  236. package/dist/cjs/nodes/ai/ai-provider-utils-sources.generated.js.map +1 -1
  237. package/dist/cjs/nodes/ai/ai-provider-utils.d.ts +31 -2
  238. package/dist/cjs/nodes/ai/ai-provider-utils.d.ts.map +1 -1
  239. package/dist/cjs/nodes/ai/ai-provider-utils.js +125 -11
  240. package/dist/cjs/nodes/ai/ai-provider-utils.js.map +1 -1
  241. package/dist/cjs/nodes/ai/ai-select-database-data.d.ts.map +1 -1
  242. package/dist/cjs/nodes/ai/ai-select-database-data.js +6 -2
  243. package/dist/cjs/nodes/ai/ai-select-database-data.js.map +1 -1
  244. package/dist/cjs/nodes/ai/ai-sentiment-analysis.d.ts.map +1 -1
  245. package/dist/cjs/nodes/ai/ai-sentiment-analysis.js +20 -5
  246. package/dist/cjs/nodes/ai/ai-sentiment-analysis.js.map +1 -1
  247. package/dist/cjs/nodes/ai/ai-summarization.d.ts.map +1 -1
  248. package/dist/cjs/nodes/ai/ai-summarization.js +20 -5
  249. package/dist/cjs/nodes/ai/ai-summarization.js.map +1 -1
  250. package/dist/cjs/nodes/ai/ai-text-classifier.d.ts.map +1 -1
  251. package/dist/cjs/nodes/ai/ai-text-classifier.js +20 -5
  252. package/dist/cjs/nodes/ai/ai-text-classifier.js.map +1 -1
  253. package/dist/cjs/nodes/ai/ai-text-transform.d.ts.map +1 -1
  254. package/dist/cjs/nodes/ai/ai-text-transform.js +20 -5
  255. package/dist/cjs/nodes/ai/ai-text-transform.js.map +1 -1
  256. package/dist/cjs/nodes/analytics/analytics-track-outcome.d.ts +3 -0
  257. package/dist/cjs/nodes/analytics/analytics-track-outcome.d.ts.map +1 -0
  258. package/dist/cjs/nodes/analytics/analytics-track-outcome.js +110 -0
  259. package/dist/cjs/nodes/analytics/analytics-track-outcome.js.map +1 -0
  260. package/dist/cjs/nodes/cart/cart-price-order.d.ts.map +1 -1
  261. package/dist/cjs/nodes/cart/cart-price-order.js +161 -2
  262. package/dist/cjs/nodes/cart/cart-price-order.js.map +1 -1
  263. package/dist/cjs/nodes/cost-bearing-node-types.d.ts +4 -0
  264. package/dist/cjs/nodes/cost-bearing-node-types.d.ts.map +1 -0
  265. package/dist/cjs/nodes/cost-bearing-node-types.js +43 -0
  266. package/dist/cjs/nodes/cost-bearing-node-types.js.map +1 -0
  267. package/dist/cjs/nodes/data/data-select.d.ts.map +1 -1
  268. package/dist/cjs/nodes/data/data-select.js +12 -2
  269. package/dist/cjs/nodes/data/data-select.js.map +1 -1
  270. package/dist/cjs/nodes/email/email-mailersend.d.ts.map +1 -1
  271. package/dist/cjs/nodes/email/email-mailersend.js +15 -11
  272. package/dist/cjs/nodes/email/email-mailersend.js.map +1 -1
  273. package/dist/cjs/nodes/email/email-mailgun.d.ts.map +1 -1
  274. package/dist/cjs/nodes/email/email-mailgun.js +15 -15
  275. package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
  276. package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
  277. package/dist/cjs/nodes/email/email-postmark.js +15 -15
  278. package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
  279. package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
  280. package/dist/cjs/nodes/email/email-resend.js +15 -15
  281. package/dist/cjs/nodes/email/email-resend.js.map +1 -1
  282. package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
  283. package/dist/cjs/nodes/email/email-sendgrid.js +15 -15
  284. package/dist/cjs/nodes/email/email-sendgrid.js.map +1 -1
  285. package/dist/cjs/nodes/email/html-escape-code.d.ts +3 -0
  286. package/dist/cjs/nodes/email/html-escape-code.d.ts.map +1 -0
  287. package/dist/cjs/nodes/email/html-escape-code.js +23 -0
  288. package/dist/cjs/nodes/email/html-escape-code.js.map +1 -0
  289. package/dist/cjs/nodes/email/message-trust.d.ts +40 -0
  290. package/dist/cjs/nodes/email/message-trust.d.ts.map +1 -0
  291. package/dist/cjs/nodes/email/message-trust.js +167 -0
  292. package/dist/cjs/nodes/email/message-trust.js.map +1 -0
  293. package/dist/cjs/nodes/email/outbound-message-guard.d.ts +113 -0
  294. package/dist/cjs/nodes/email/outbound-message-guard.d.ts.map +1 -0
  295. package/dist/cjs/nodes/email/outbound-message-guard.js +460 -0
  296. package/dist/cjs/nodes/email/outbound-message-guard.js.map +1 -0
  297. package/dist/cjs/nodes/email/trusted-html-code.d.ts +22 -0
  298. package/dist/cjs/nodes/email/trusted-html-code.d.ts.map +1 -0
  299. package/dist/cjs/nodes/email/trusted-html-code.js +52 -0
  300. package/dist/cjs/nodes/email/trusted-html-code.js.map +1 -0
  301. package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
  302. package/dist/cjs/nodes/general/general-custom-js.js +44 -7
  303. package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
  304. package/dist/cjs/nodes/general/general-http-request.d.ts.map +1 -1
  305. package/dist/cjs/nodes/general/general-http-request.js +431 -45
  306. package/dist/cjs/nodes/general/general-http-request.js.map +1 -1
  307. package/dist/cjs/nodes/general/general-rate-limiter.d.ts.map +1 -1
  308. package/dist/cjs/nodes/general/general-rate-limiter.js +46 -78
  309. package/dist/cjs/nodes/general/general-rate-limiter.js.map +1 -1
  310. package/dist/cjs/nodes/general/general-wait.d.ts +3 -0
  311. package/dist/cjs/nodes/general/general-wait.d.ts.map +1 -0
  312. package/dist/cjs/nodes/general/general-wait.js +81 -0
  313. package/dist/cjs/nodes/general/general-wait.js.map +1 -0
  314. package/dist/cjs/nodes/general/visitor-request.d.ts +44 -0
  315. package/dist/cjs/nodes/general/visitor-request.d.ts.map +1 -0
  316. package/dist/cjs/nodes/general/visitor-request.js +72 -0
  317. package/dist/cjs/nodes/general/visitor-request.js.map +1 -0
  318. package/dist/cjs/nodes/index.d.ts.map +1 -1
  319. package/dist/cjs/nodes/index.js +7 -1
  320. package/dist/cjs/nodes/index.js.map +1 -1
  321. package/dist/cjs/nodes/integrations/integration-slack.d.ts.map +1 -1
  322. package/dist/cjs/nodes/integrations/integration-slack.js +17 -6
  323. package/dist/cjs/nodes/integrations/integration-slack.js.map +1 -1
  324. package/dist/cjs/nodes/newsletter/newsletter-confirm.d.ts +3 -0
  325. package/dist/cjs/nodes/newsletter/newsletter-confirm.d.ts.map +1 -0
  326. package/dist/cjs/nodes/newsletter/newsletter-confirm.js +41 -0
  327. package/dist/cjs/nodes/newsletter/newsletter-confirm.js.map +1 -0
  328. package/dist/cjs/nodes/newsletter/newsletter-subscribe.d.ts +7 -0
  329. package/dist/cjs/nodes/newsletter/newsletter-subscribe.d.ts.map +1 -0
  330. package/dist/cjs/nodes/newsletter/newsletter-subscribe.js +69 -0
  331. package/dist/cjs/nodes/newsletter/newsletter-subscribe.js.map +1 -0
  332. package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.d.ts +3 -0
  333. package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.d.ts.map +1 -0
  334. package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.js +42 -0
  335. package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.js.map +1 -0
  336. package/dist/cjs/nodes/realtime/realtime-join-channel.d.ts.map +1 -1
  337. package/dist/cjs/nodes/realtime/realtime-join-channel.js +5 -4
  338. package/dist/cjs/nodes/realtime/realtime-join-channel.js.map +1 -1
  339. package/dist/cjs/nodes/realtime/realtime-send-channel-event.d.ts.map +1 -1
  340. package/dist/cjs/nodes/realtime/realtime-send-channel-event.js +11 -2
  341. package/dist/cjs/nodes/realtime/realtime-send-channel-event.js.map +1 -1
  342. package/dist/cjs/nodes/realtime/realtime-send-channel-message.d.ts.map +1 -1
  343. package/dist/cjs/nodes/realtime/realtime-send-channel-message.js +11 -2
  344. package/dist/cjs/nodes/realtime/realtime-send-channel-message.js.map +1 -1
  345. package/dist/cjs/nodes/sms/sms-infobip.d.ts.map +1 -1
  346. package/dist/cjs/nodes/sms/sms-infobip.js +11 -8
  347. package/dist/cjs/nodes/sms/sms-infobip.js.map +1 -1
  348. package/dist/cjs/nodes/sms/sms-smsapi.d.ts.map +1 -1
  349. package/dist/cjs/nodes/sms/sms-smsapi.js +11 -8
  350. package/dist/cjs/nodes/sms/sms-smsapi.js.map +1 -1
  351. package/dist/cjs/nodes/sms/sms-textmagic.d.ts.map +1 -1
  352. package/dist/cjs/nodes/sms/sms-textmagic.js +11 -8
  353. package/dist/cjs/nodes/sms/sms-textmagic.js.map +1 -1
  354. package/dist/cjs/nodes/sms/sms-twilio.d.ts.map +1 -1
  355. package/dist/cjs/nodes/sms/sms-twilio.js +12 -10
  356. package/dist/cjs/nodes/sms/sms-twilio.js.map +1 -1
  357. package/dist/cjs/nodes/types.d.ts +8 -1
  358. package/dist/cjs/nodes/types.d.ts.map +1 -1
  359. package/dist/cjs/nodes/utility/utility-similarity-scoring.d.ts.map +1 -1
  360. package/dist/cjs/nodes/utility/utility-similarity-scoring.js +29 -10
  361. package/dist/cjs/nodes/utility/utility-similarity-scoring.js.map +1 -1
  362. package/dist/cjs/page-access-guard-generator.d.ts +23 -0
  363. package/dist/cjs/page-access-guard-generator.d.ts.map +1 -0
  364. package/dist/cjs/page-access-guard-generator.js +135 -0
  365. package/dist/cjs/page-access-guard-generator.js.map +1 -0
  366. package/dist/cjs/password-reset-route-generator.d.ts +119 -0
  367. package/dist/cjs/password-reset-route-generator.d.ts.map +1 -0
  368. package/dist/cjs/password-reset-route-generator.js +460 -0
  369. package/dist/cjs/password-reset-route-generator.js.map +1 -0
  370. package/dist/cjs/payments/payment-core-code.d.ts.map +1 -1
  371. package/dist/cjs/payments/payment-core-code.js +12 -7
  372. package/dist/cjs/payments/payment-core-code.js.map +1 -1
  373. package/dist/cjs/project-code-scanner.d.ts +11 -0
  374. package/dist/cjs/project-code-scanner.d.ts.map +1 -0
  375. package/dist/cjs/project-code-scanner.js +80 -0
  376. package/dist/cjs/project-code-scanner.js.map +1 -0
  377. package/dist/cjs/public-step-guards.d.ts +122 -0
  378. package/dist/cjs/public-step-guards.d.ts.map +1 -0
  379. package/dist/cjs/public-step-guards.js +288 -0
  380. package/dist/cjs/public-step-guards.js.map +1 -0
  381. package/dist/cjs/publish-refusals.d.ts +66 -0
  382. package/dist/cjs/publish-refusals.d.ts.map +1 -0
  383. package/dist/cjs/publish-refusals.js +278 -0
  384. package/dist/cjs/publish-refusals.js.map +1 -0
  385. package/dist/cjs/realtime-generator.d.ts +63 -1
  386. package/dist/cjs/realtime-generator.d.ts.map +1 -1
  387. package/dist/cjs/realtime-generator.js +404 -230
  388. package/dist/cjs/realtime-generator.js.map +1 -1
  389. package/dist/cjs/runtime-storage-generator.d.ts +32 -1
  390. package/dist/cjs/runtime-storage-generator.d.ts.map +1 -1
  391. package/dist/cjs/runtime-storage-generator.js +265 -12
  392. package/dist/cjs/runtime-storage-generator.js.map +1 -1
  393. package/dist/cjs/security-runtime/index.d.ts +14 -0
  394. package/dist/cjs/security-runtime/index.d.ts.map +1 -0
  395. package/dist/cjs/security-runtime/index.js +48 -0
  396. package/dist/cjs/security-runtime/index.js.map +1 -0
  397. package/dist/cjs/security-runtime/security-runtime-code.d.ts +53 -0
  398. package/dist/cjs/security-runtime/security-runtime-code.d.ts.map +1 -0
  399. package/dist/cjs/security-runtime/security-runtime-code.js +580 -0
  400. package/dist/cjs/security-runtime/security-runtime-code.js.map +1 -0
  401. package/dist/cjs/security-scanner.d.ts +72 -3
  402. package/dist/cjs/security-scanner.d.ts.map +1 -1
  403. package/dist/cjs/security-scanner.js +220 -8
  404. package/dist/cjs/security-scanner.js.map +1 -1
  405. package/dist/cjs/segment-splitter.d.ts.map +1 -1
  406. package/dist/cjs/segment-splitter.js +10 -2
  407. package/dist/cjs/segment-splitter.js.map +1 -1
  408. package/dist/cjs/segment-trust.d.ts +36 -0
  409. package/dist/cjs/segment-trust.d.ts.map +1 -1
  410. package/dist/cjs/segment-trust.js +147 -3
  411. package/dist/cjs/segment-trust.js.map +1 -1
  412. package/dist/cjs/self-call-headers.d.ts +31 -0
  413. package/dist/cjs/self-call-headers.d.ts.map +1 -0
  414. package/dist/cjs/self-call-headers.js +48 -0
  415. package/dist/cjs/self-call-headers.js.map +1 -0
  416. package/dist/cjs/sent-email-log/index.d.ts +7 -0
  417. package/dist/cjs/sent-email-log/index.d.ts.map +1 -1
  418. package/dist/cjs/sent-email-log/index.js +17 -1
  419. package/dist/cjs/sent-email-log/index.js.map +1 -1
  420. package/dist/cjs/sent-email-log/sent-email-log-code.d.ts +7 -0
  421. package/dist/cjs/sent-email-log/sent-email-log-code.d.ts.map +1 -1
  422. package/dist/cjs/sent-email-log/sent-email-log-code.js +17 -3
  423. package/dist/cjs/sent-email-log/sent-email-log-code.js.map +1 -1
  424. package/dist/cjs/server-custom-node-call.d.ts +79 -0
  425. package/dist/cjs/server-custom-node-call.d.ts.map +1 -0
  426. package/dist/cjs/server-custom-node-call.js +285 -0
  427. package/dist/cjs/server-custom-node-call.js.map +1 -0
  428. package/dist/cjs/server-runtime-code.d.ts +39 -12
  429. package/dist/cjs/server-runtime-code.d.ts.map +1 -1
  430. package/dist/cjs/server-runtime-code.js +484 -41
  431. package/dist/cjs/server-runtime-code.js.map +1 -1
  432. package/dist/cjs/session-cookie-resolver.d.ts +3 -1
  433. package/dist/cjs/session-cookie-resolver.d.ts.map +1 -1
  434. package/dist/cjs/session-revocation-generator.d.ts +39 -0
  435. package/dist/cjs/session-revocation-generator.d.ts.map +1 -0
  436. package/dist/cjs/session-revocation-generator.js +177 -0
  437. package/dist/cjs/session-revocation-generator.js.map +1 -0
  438. package/dist/cjs/slack-message-values.d.ts +6 -0
  439. package/dist/cjs/slack-message-values.d.ts.map +1 -0
  440. package/dist/cjs/slack-message-values.js +73 -0
  441. package/dist/cjs/slack-message-values.js.map +1 -0
  442. package/dist/cjs/transactional-email-code.d.ts +11 -0
  443. package/dist/cjs/transactional-email-code.d.ts.map +1 -1
  444. package/dist/cjs/transactional-email-code.js +29 -5
  445. package/dist/cjs/transactional-email-code.js.map +1 -1
  446. package/dist/cjs/trigger-generator.d.ts +2 -0
  447. package/dist/cjs/trigger-generator.d.ts.map +1 -1
  448. package/dist/cjs/trigger-generator.js +17 -4
  449. package/dist/cjs/trigger-generator.js.map +1 -1
  450. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  451. package/dist/cjs/visitor-step-limits.d.ts +66 -0
  452. package/dist/cjs/visitor-step-limits.d.ts.map +1 -0
  453. package/dist/cjs/visitor-step-limits.js +187 -0
  454. package/dist/cjs/visitor-step-limits.js.map +1 -0
  455. package/dist/cjs/webhook-generator.d.ts.map +1 -1
  456. package/dist/cjs/webhook-generator.js +93 -15
  457. package/dist/cjs/webhook-generator.js.map +1 -1
  458. package/dist/cjs/webhook-signature-verification.d.ts.map +1 -1
  459. package/dist/cjs/webhook-signature-verification.js +13 -3
  460. package/dist/cjs/webhook-signature-verification.js.map +1 -1
  461. package/dist/cjs/workflow-auth-generator.d.ts +4 -1
  462. package/dist/cjs/workflow-auth-generator.d.ts.map +1 -1
  463. package/dist/cjs/workflow-auth-generator.js +8 -3
  464. package/dist/cjs/workflow-auth-generator.js.map +1 -1
  465. package/dist/cjs/workflow-component-plugin.js +11 -3
  466. package/dist/cjs/workflow-component-plugin.js.map +1 -1
  467. package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
  468. package/dist/cjs/workflow-project-plugin.js +302 -56
  469. package/dist/cjs/workflow-project-plugin.js.map +1 -1
  470. package/dist/cjs/workflow-scope.d.ts.map +1 -1
  471. package/dist/cjs/workflow-scope.js +5 -0
  472. package/dist/cjs/workflow-scope.js.map +1 -1
  473. package/dist/esm/account-delete-route-generator.d.ts +1 -0
  474. package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
  475. package/dist/esm/account-delete-route-generator.js +18 -3
  476. package/dist/esm/account-delete-route-generator.js.map +1 -1
  477. package/dist/esm/api-route-generator.d.ts +52 -3
  478. package/dist/esm/api-route-generator.d.ts.map +1 -1
  479. package/dist/esm/api-route-generator.js +595 -142
  480. package/dist/esm/api-route-generator.js.map +1 -1
  481. package/dist/esm/auth-generator.d.ts +126 -3
  482. package/dist/esm/auth-generator.d.ts.map +1 -1
  483. package/dist/esm/auth-generator.js +1350 -140
  484. package/dist/esm/auth-generator.js.map +1 -1
  485. package/dist/esm/automations/activity-kicks.d.ts +12 -0
  486. package/dist/esm/automations/activity-kicks.d.ts.map +1 -0
  487. package/dist/esm/automations/activity-kicks.js +118 -0
  488. package/dist/esm/automations/activity-kicks.js.map +1 -0
  489. package/dist/esm/automations/app-events-source.d.ts +33 -0
  490. package/dist/esm/automations/app-events-source.d.ts.map +1 -0
  491. package/dist/esm/automations/app-events-source.js +104 -0
  492. package/dist/esm/automations/app-events-source.js.map +1 -0
  493. package/dist/esm/automations/automations-project-plugin.d.ts +21 -0
  494. package/dist/esm/automations/automations-project-plugin.d.ts.map +1 -0
  495. package/dist/esm/automations/automations-project-plugin.js +98 -0
  496. package/dist/esm/automations/automations-project-plugin.js.map +1 -0
  497. package/dist/esm/automations/automations-scope.d.ts +104 -0
  498. package/dist/esm/automations/automations-scope.d.ts.map +1 -0
  499. package/dist/esm/automations/automations-scope.js +165 -0
  500. package/dist/esm/automations/automations-scope.js.map +1 -0
  501. package/dist/esm/automations/dispatcher-source.d.ts +72 -0
  502. package/dist/esm/automations/dispatcher-source.d.ts.map +1 -0
  503. package/dist/esm/automations/dispatcher-source.js +999 -0
  504. package/dist/esm/automations/dispatcher-source.js.map +1 -0
  505. package/dist/esm/automations/event-bus-source.d.ts +44 -0
  506. package/dist/esm/automations/event-bus-source.d.ts.map +1 -0
  507. package/dist/esm/automations/event-bus-source.js +421 -0
  508. package/dist/esm/automations/event-bus-source.js.map +1 -0
  509. package/dist/esm/automations/handler-module-generator.d.ts +25 -0
  510. package/dist/esm/automations/handler-module-generator.d.ts.map +1 -0
  511. package/dist/esm/automations/handler-module-generator.js +71 -0
  512. package/dist/esm/automations/handler-module-generator.js.map +1 -0
  513. package/dist/esm/automations/hydrators-source.d.ts +33 -0
  514. package/dist/esm/automations/hydrators-source.d.ts.map +1 -0
  515. package/dist/esm/automations/hydrators-source.js +567 -0
  516. package/dist/esm/automations/hydrators-source.js.map +1 -0
  517. package/dist/esm/booking-calendar-feed-route-generator.d.ts.map +1 -1
  518. package/dist/esm/booking-calendar-feed-route-generator.js +8 -0
  519. package/dist/esm/booking-calendar-feed-route-generator.js.map +1 -1
  520. package/dist/esm/configured-nextauth-url.d.ts +17 -0
  521. package/dist/esm/configured-nextauth-url.d.ts.map +1 -0
  522. package/dist/esm/configured-nextauth-url.js +17 -0
  523. package/dist/esm/configured-nextauth-url.js.map +1 -0
  524. package/dist/esm/cron-request-verification.d.ts +22 -1
  525. package/dist/esm/cron-request-verification.d.ts.map +1 -1
  526. package/dist/esm/cron-request-verification.js +48 -5
  527. package/dist/esm/cron-request-verification.js.map +1 -1
  528. package/dist/esm/data-api-route-generator.d.ts +19 -0
  529. package/dist/esm/data-api-route-generator.d.ts.map +1 -1
  530. package/dist/esm/data-api-route-generator.js +710 -43
  531. package/dist/esm/data-api-route-generator.js.map +1 -1
  532. package/dist/esm/ecommerce/parameterized-stock-chain.d.ts +3 -1
  533. package/dist/esm/ecommerce/parameterized-stock-chain.d.ts.map +1 -1
  534. package/dist/esm/ecommerce/parameterized-stock-chain.js +43 -4
  535. package/dist/esm/ecommerce/parameterized-stock-chain.js.map +1 -1
  536. package/dist/esm/ecommerce/place-order-guards.d.ts +126 -0
  537. package/dist/esm/ecommerce/place-order-guards.d.ts.map +1 -0
  538. package/dist/esm/ecommerce/place-order-guards.js +655 -0
  539. package/dist/esm/ecommerce/place-order-guards.js.map +1 -0
  540. package/dist/esm/ecommerce-customhandler-rewriter.d.ts +2 -0
  541. package/dist/esm/ecommerce-customhandler-rewriter.d.ts.map +1 -1
  542. package/dist/esm/ecommerce-customhandler-rewriter.js +38 -2
  543. package/dist/esm/ecommerce-customhandler-rewriter.js.map +1 -1
  544. package/dist/esm/executor-generator.d.ts.map +1 -1
  545. package/dist/esm/executor-generator.js +368 -60
  546. package/dist/esm/executor-generator.js.map +1 -1
  547. package/dist/esm/graph-utils.d.ts +9 -0
  548. package/dist/esm/graph-utils.d.ts.map +1 -1
  549. package/dist/esm/graph-utils.js +30 -0
  550. package/dist/esm/graph-utils.js.map +1 -1
  551. package/dist/esm/index.d.ts +20 -1
  552. package/dist/esm/index.d.ts.map +1 -1
  553. package/dist/esm/index.js +27 -1
  554. package/dist/esm/index.js.map +1 -1
  555. package/dist/esm/invoice/api-routes-code.d.ts +9 -3
  556. package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
  557. package/dist/esm/invoice/api-routes-code.js +9 -7
  558. package/dist/esm/invoice/api-routes-code.js.map +1 -1
  559. package/dist/esm/invoice/index.d.ts.map +1 -1
  560. package/dist/esm/invoice/index.js +12 -7
  561. package/dist/esm/invoice/index.js.map +1 -1
  562. package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
  563. package/dist/esm/invoice/invoice-assembly-code.js +8 -3
  564. package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
  565. package/dist/esm/invoice/pdf-generator-code.d.ts.map +1 -1
  566. package/dist/esm/invoice/pdf-generator-code.js +2 -1
  567. package/dist/esm/invoice/pdf-generator-code.js.map +1 -1
  568. package/dist/esm/invoice/pdf-service-client-code.d.ts.map +1 -1
  569. package/dist/esm/invoice/pdf-service-client-code.js +15 -7
  570. package/dist/esm/invoice/pdf-service-client-code.js.map +1 -1
  571. package/dist/esm/newsletter/admin-route-code.d.ts +60 -0
  572. package/dist/esm/newsletter/admin-route-code.d.ts.map +1 -0
  573. package/dist/esm/newsletter/admin-route-code.js +295 -0
  574. package/dist/esm/newsletter/admin-route-code.js.map +1 -0
  575. package/dist/esm/newsletter/helper-code.d.ts +109 -0
  576. package/dist/esm/newsletter/helper-code.d.ts.map +1 -0
  577. package/dist/esm/newsletter/helper-code.js +656 -0
  578. package/dist/esm/newsletter/helper-code.js.map +1 -0
  579. package/dist/esm/newsletter/newsletter-scope.d.ts +65 -0
  580. package/dist/esm/newsletter/newsletter-scope.d.ts.map +1 -0
  581. package/dist/esm/newsletter/newsletter-scope.js +97 -0
  582. package/dist/esm/newsletter/newsletter-scope.js.map +1 -0
  583. package/dist/esm/newsletter/project-plugin.d.ts +18 -0
  584. package/dist/esm/newsletter/project-plugin.d.ts.map +1 -0
  585. package/dist/esm/newsletter/project-plugin.js +93 -0
  586. package/dist/esm/newsletter/project-plugin.js.map +1 -0
  587. package/dist/esm/newsletter/routes-code.d.ts +6 -0
  588. package/dist/esm/newsletter/routes-code.d.ts.map +1 -0
  589. package/dist/esm/newsletter/routes-code.js +175 -0
  590. package/dist/esm/newsletter/routes-code.js.map +1 -0
  591. package/dist/esm/nodes/account/account-compare-passwords.d.ts.map +1 -1
  592. package/dist/esm/nodes/account/account-compare-passwords.js +16 -6
  593. package/dist/esm/nodes/account/account-compare-passwords.js.map +1 -1
  594. package/dist/esm/nodes/account/account-delete-current.js +1 -1
  595. package/dist/esm/nodes/account/account-get-current.js +1 -1
  596. package/dist/esm/nodes/account/account-hash-password.d.ts.map +1 -1
  597. package/dist/esm/nodes/account/account-hash-password.js +15 -3
  598. package/dist/esm/nodes/account/account-hash-password.js.map +1 -1
  599. package/dist/esm/nodes/account/account-login.d.ts.map +1 -1
  600. package/dist/esm/nodes/account/account-login.js +23 -6
  601. package/dist/esm/nodes/account/account-login.js.map +1 -1
  602. package/dist/esm/nodes/account/account-logout.js +1 -1
  603. package/dist/esm/nodes/account/account-refresh-session.js +1 -1
  604. package/dist/esm/nodes/account/account-request-password-reset.d.ts +3 -0
  605. package/dist/esm/nodes/account/account-request-password-reset.d.ts.map +1 -0
  606. package/dist/esm/nodes/account/account-request-password-reset.js +43 -0
  607. package/dist/esm/nodes/account/account-request-password-reset.js.map +1 -0
  608. package/dist/esm/nodes/account/account-reset-password.d.ts +3 -0
  609. package/dist/esm/nodes/account/account-reset-password.d.ts.map +1 -0
  610. package/dist/esm/nodes/account/account-reset-password.js +41 -0
  611. package/dist/esm/nodes/account/account-reset-password.js.map +1 -0
  612. package/dist/esm/nodes/account/account-signup.d.ts.map +1 -1
  613. package/dist/esm/nodes/account/account-signup.js +82 -19
  614. package/dist/esm/nodes/account/account-signup.js.map +1 -1
  615. package/dist/esm/nodes/account/account-social-login.js +1 -1
  616. package/dist/esm/nodes/ai/ai-custom-prompt.d.ts.map +1 -1
  617. package/dist/esm/nodes/ai/ai-custom-prompt.js +20 -5
  618. package/dist/esm/nodes/ai/ai-custom-prompt.js.map +1 -1
  619. package/dist/esm/nodes/ai/ai-detect-language.d.ts.map +1 -1
  620. package/dist/esm/nodes/ai/ai-detect-language.js +20 -5
  621. package/dist/esm/nodes/ai/ai-detect-language.js.map +1 -1
  622. package/dist/esm/nodes/ai/ai-generate-text-embedding.d.ts.map +1 -1
  623. package/dist/esm/nodes/ai/ai-generate-text-embedding.js +10 -2
  624. package/dist/esm/nodes/ai/ai-generate-text-embedding.js.map +1 -1
  625. package/dist/esm/nodes/ai/ai-provider-utils-sources.generated.js +2 -2
  626. package/dist/esm/nodes/ai/ai-provider-utils-sources.generated.js.map +1 -1
  627. package/dist/esm/nodes/ai/ai-provider-utils.d.ts +31 -2
  628. package/dist/esm/nodes/ai/ai-provider-utils.d.ts.map +1 -1
  629. package/dist/esm/nodes/ai/ai-provider-utils.js +124 -10
  630. package/dist/esm/nodes/ai/ai-provider-utils.js.map +1 -1
  631. package/dist/esm/nodes/ai/ai-select-database-data.d.ts.map +1 -1
  632. package/dist/esm/nodes/ai/ai-select-database-data.js +6 -2
  633. package/dist/esm/nodes/ai/ai-select-database-data.js.map +1 -1
  634. package/dist/esm/nodes/ai/ai-sentiment-analysis.d.ts.map +1 -1
  635. package/dist/esm/nodes/ai/ai-sentiment-analysis.js +20 -5
  636. package/dist/esm/nodes/ai/ai-sentiment-analysis.js.map +1 -1
  637. package/dist/esm/nodes/ai/ai-summarization.d.ts.map +1 -1
  638. package/dist/esm/nodes/ai/ai-summarization.js +20 -5
  639. package/dist/esm/nodes/ai/ai-summarization.js.map +1 -1
  640. package/dist/esm/nodes/ai/ai-text-classifier.d.ts.map +1 -1
  641. package/dist/esm/nodes/ai/ai-text-classifier.js +20 -5
  642. package/dist/esm/nodes/ai/ai-text-classifier.js.map +1 -1
  643. package/dist/esm/nodes/ai/ai-text-transform.d.ts.map +1 -1
  644. package/dist/esm/nodes/ai/ai-text-transform.js +20 -5
  645. package/dist/esm/nodes/ai/ai-text-transform.js.map +1 -1
  646. package/dist/esm/nodes/analytics/analytics-track-outcome.d.ts +3 -0
  647. package/dist/esm/nodes/analytics/analytics-track-outcome.d.ts.map +1 -0
  648. package/dist/esm/nodes/analytics/analytics-track-outcome.js +107 -0
  649. package/dist/esm/nodes/analytics/analytics-track-outcome.js.map +1 -0
  650. package/dist/esm/nodes/cart/cart-price-order.d.ts.map +1 -1
  651. package/dist/esm/nodes/cart/cart-price-order.js +161 -2
  652. package/dist/esm/nodes/cart/cart-price-order.js.map +1 -1
  653. package/dist/esm/nodes/cost-bearing-node-types.d.ts +4 -0
  654. package/dist/esm/nodes/cost-bearing-node-types.d.ts.map +1 -0
  655. package/dist/esm/nodes/cost-bearing-node-types.js +39 -0
  656. package/dist/esm/nodes/cost-bearing-node-types.js.map +1 -0
  657. package/dist/esm/nodes/data/data-select.d.ts.map +1 -1
  658. package/dist/esm/nodes/data/data-select.js +12 -2
  659. package/dist/esm/nodes/data/data-select.js.map +1 -1
  660. package/dist/esm/nodes/email/email-mailersend.d.ts.map +1 -1
  661. package/dist/esm/nodes/email/email-mailersend.js +15 -11
  662. package/dist/esm/nodes/email/email-mailersend.js.map +1 -1
  663. package/dist/esm/nodes/email/email-mailgun.d.ts.map +1 -1
  664. package/dist/esm/nodes/email/email-mailgun.js +15 -15
  665. package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
  666. package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
  667. package/dist/esm/nodes/email/email-postmark.js +15 -15
  668. package/dist/esm/nodes/email/email-postmark.js.map +1 -1
  669. package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
  670. package/dist/esm/nodes/email/email-resend.js +15 -15
  671. package/dist/esm/nodes/email/email-resend.js.map +1 -1
  672. package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
  673. package/dist/esm/nodes/email/email-sendgrid.js +15 -15
  674. package/dist/esm/nodes/email/email-sendgrid.js.map +1 -1
  675. package/dist/esm/nodes/email/html-escape-code.d.ts +3 -0
  676. package/dist/esm/nodes/email/html-escape-code.d.ts.map +1 -0
  677. package/dist/esm/nodes/email/html-escape-code.js +18 -0
  678. package/dist/esm/nodes/email/html-escape-code.js.map +1 -0
  679. package/dist/esm/nodes/email/message-trust.d.ts +40 -0
  680. package/dist/esm/nodes/email/message-trust.d.ts.map +1 -0
  681. package/dist/esm/nodes/email/message-trust.js +161 -0
  682. package/dist/esm/nodes/email/message-trust.js.map +1 -0
  683. package/dist/esm/nodes/email/outbound-message-guard.d.ts +113 -0
  684. package/dist/esm/nodes/email/outbound-message-guard.d.ts.map +1 -0
  685. package/dist/esm/nodes/email/outbound-message-guard.js +452 -0
  686. package/dist/esm/nodes/email/outbound-message-guard.js.map +1 -0
  687. package/dist/esm/nodes/email/trusted-html-code.d.ts +22 -0
  688. package/dist/esm/nodes/email/trusted-html-code.d.ts.map +1 -0
  689. package/dist/esm/nodes/email/trusted-html-code.js +49 -0
  690. package/dist/esm/nodes/email/trusted-html-code.js.map +1 -0
  691. package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
  692. package/dist/esm/nodes/general/general-custom-js.js +44 -7
  693. package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
  694. package/dist/esm/nodes/general/general-http-request.d.ts.map +1 -1
  695. package/dist/esm/nodes/general/general-http-request.js +431 -45
  696. package/dist/esm/nodes/general/general-http-request.js.map +1 -1
  697. package/dist/esm/nodes/general/general-rate-limiter.d.ts.map +1 -1
  698. package/dist/esm/nodes/general/general-rate-limiter.js +46 -78
  699. package/dist/esm/nodes/general/general-rate-limiter.js.map +1 -1
  700. package/dist/esm/nodes/general/general-wait.d.ts +3 -0
  701. package/dist/esm/nodes/general/general-wait.d.ts.map +1 -0
  702. package/dist/esm/nodes/general/general-wait.js +78 -0
  703. package/dist/esm/nodes/general/general-wait.js.map +1 -0
  704. package/dist/esm/nodes/general/visitor-request.d.ts +44 -0
  705. package/dist/esm/nodes/general/visitor-request.d.ts.map +1 -0
  706. package/dist/esm/nodes/general/visitor-request.js +65 -0
  707. package/dist/esm/nodes/general/visitor-request.js.map +1 -0
  708. package/dist/esm/nodes/index.d.ts.map +1 -1
  709. package/dist/esm/nodes/index.js +7 -1
  710. package/dist/esm/nodes/index.js.map +1 -1
  711. package/dist/esm/nodes/integrations/integration-slack.d.ts.map +1 -1
  712. package/dist/esm/nodes/integrations/integration-slack.js +17 -6
  713. package/dist/esm/nodes/integrations/integration-slack.js.map +1 -1
  714. package/dist/esm/nodes/newsletter/newsletter-confirm.d.ts +3 -0
  715. package/dist/esm/nodes/newsletter/newsletter-confirm.d.ts.map +1 -0
  716. package/dist/esm/nodes/newsletter/newsletter-confirm.js +38 -0
  717. package/dist/esm/nodes/newsletter/newsletter-confirm.js.map +1 -0
  718. package/dist/esm/nodes/newsletter/newsletter-subscribe.d.ts +7 -0
  719. package/dist/esm/nodes/newsletter/newsletter-subscribe.d.ts.map +1 -0
  720. package/dist/esm/nodes/newsletter/newsletter-subscribe.js +66 -0
  721. package/dist/esm/nodes/newsletter/newsletter-subscribe.js.map +1 -0
  722. package/dist/esm/nodes/newsletter/newsletter-unsubscribe.d.ts +3 -0
  723. package/dist/esm/nodes/newsletter/newsletter-unsubscribe.d.ts.map +1 -0
  724. package/dist/esm/nodes/newsletter/newsletter-unsubscribe.js +39 -0
  725. package/dist/esm/nodes/newsletter/newsletter-unsubscribe.js.map +1 -0
  726. package/dist/esm/nodes/realtime/realtime-join-channel.d.ts.map +1 -1
  727. package/dist/esm/nodes/realtime/realtime-join-channel.js +5 -4
  728. package/dist/esm/nodes/realtime/realtime-join-channel.js.map +1 -1
  729. package/dist/esm/nodes/realtime/realtime-send-channel-event.d.ts.map +1 -1
  730. package/dist/esm/nodes/realtime/realtime-send-channel-event.js +11 -2
  731. package/dist/esm/nodes/realtime/realtime-send-channel-event.js.map +1 -1
  732. package/dist/esm/nodes/realtime/realtime-send-channel-message.d.ts.map +1 -1
  733. package/dist/esm/nodes/realtime/realtime-send-channel-message.js +11 -2
  734. package/dist/esm/nodes/realtime/realtime-send-channel-message.js.map +1 -1
  735. package/dist/esm/nodes/sms/sms-infobip.d.ts.map +1 -1
  736. package/dist/esm/nodes/sms/sms-infobip.js +11 -8
  737. package/dist/esm/nodes/sms/sms-infobip.js.map +1 -1
  738. package/dist/esm/nodes/sms/sms-smsapi.d.ts.map +1 -1
  739. package/dist/esm/nodes/sms/sms-smsapi.js +11 -8
  740. package/dist/esm/nodes/sms/sms-smsapi.js.map +1 -1
  741. package/dist/esm/nodes/sms/sms-textmagic.d.ts.map +1 -1
  742. package/dist/esm/nodes/sms/sms-textmagic.js +11 -8
  743. package/dist/esm/nodes/sms/sms-textmagic.js.map +1 -1
  744. package/dist/esm/nodes/sms/sms-twilio.d.ts.map +1 -1
  745. package/dist/esm/nodes/sms/sms-twilio.js +12 -10
  746. package/dist/esm/nodes/sms/sms-twilio.js.map +1 -1
  747. package/dist/esm/nodes/types.d.ts +8 -1
  748. package/dist/esm/nodes/types.d.ts.map +1 -1
  749. package/dist/esm/nodes/utility/utility-similarity-scoring.d.ts.map +1 -1
  750. package/dist/esm/nodes/utility/utility-similarity-scoring.js +29 -10
  751. package/dist/esm/nodes/utility/utility-similarity-scoring.js.map +1 -1
  752. package/dist/esm/page-access-guard-generator.d.ts +23 -0
  753. package/dist/esm/page-access-guard-generator.d.ts.map +1 -0
  754. package/dist/esm/page-access-guard-generator.js +131 -0
  755. package/dist/esm/page-access-guard-generator.js.map +1 -0
  756. package/dist/esm/password-reset-route-generator.d.ts +119 -0
  757. package/dist/esm/password-reset-route-generator.d.ts.map +1 -0
  758. package/dist/esm/password-reset-route-generator.js +448 -0
  759. package/dist/esm/password-reset-route-generator.js.map +1 -0
  760. package/dist/esm/payments/payment-core-code.d.ts.map +1 -1
  761. package/dist/esm/payments/payment-core-code.js +12 -7
  762. package/dist/esm/payments/payment-core-code.js.map +1 -1
  763. package/dist/esm/project-code-scanner.d.ts +11 -0
  764. package/dist/esm/project-code-scanner.d.ts.map +1 -0
  765. package/dist/esm/project-code-scanner.js +75 -0
  766. package/dist/esm/project-code-scanner.js.map +1 -0
  767. package/dist/esm/public-step-guards.d.ts +122 -0
  768. package/dist/esm/public-step-guards.d.ts.map +1 -0
  769. package/dist/esm/public-step-guards.js +280 -0
  770. package/dist/esm/public-step-guards.js.map +1 -0
  771. package/dist/esm/publish-refusals.d.ts +66 -0
  772. package/dist/esm/publish-refusals.d.ts.map +1 -0
  773. package/dist/esm/publish-refusals.js +271 -0
  774. package/dist/esm/publish-refusals.js.map +1 -0
  775. package/dist/esm/realtime-generator.d.ts +63 -1
  776. package/dist/esm/realtime-generator.d.ts.map +1 -1
  777. package/dist/esm/realtime-generator.js +401 -229
  778. package/dist/esm/realtime-generator.js.map +1 -1
  779. package/dist/esm/runtime-storage-generator.d.ts +32 -1
  780. package/dist/esm/runtime-storage-generator.d.ts.map +1 -1
  781. package/dist/esm/runtime-storage-generator.js +264 -11
  782. package/dist/esm/runtime-storage-generator.js.map +1 -1
  783. package/dist/esm/security-runtime/index.d.ts +14 -0
  784. package/dist/esm/security-runtime/index.d.ts.map +1 -0
  785. package/dist/esm/security-runtime/index.js +37 -0
  786. package/dist/esm/security-runtime/index.js.map +1 -0
  787. package/dist/esm/security-runtime/security-runtime-code.d.ts +53 -0
  788. package/dist/esm/security-runtime/security-runtime-code.d.ts.map +1 -0
  789. package/dist/esm/security-runtime/security-runtime-code.js +575 -0
  790. package/dist/esm/security-runtime/security-runtime-code.js.map +1 -0
  791. package/dist/esm/security-scanner.d.ts +72 -3
  792. package/dist/esm/security-scanner.d.ts.map +1 -1
  793. package/dist/esm/security-scanner.js +217 -8
  794. package/dist/esm/security-scanner.js.map +1 -1
  795. package/dist/esm/segment-splitter.d.ts.map +1 -1
  796. package/dist/esm/segment-splitter.js +10 -2
  797. package/dist/esm/segment-splitter.js.map +1 -1
  798. package/dist/esm/segment-trust.d.ts +36 -0
  799. package/dist/esm/segment-trust.d.ts.map +1 -1
  800. package/dist/esm/segment-trust.js +146 -2
  801. package/dist/esm/segment-trust.js.map +1 -1
  802. package/dist/esm/self-call-headers.d.ts +31 -0
  803. package/dist/esm/self-call-headers.d.ts.map +1 -0
  804. package/dist/esm/self-call-headers.js +43 -0
  805. package/dist/esm/self-call-headers.js.map +1 -0
  806. package/dist/esm/sent-email-log/index.d.ts +7 -0
  807. package/dist/esm/sent-email-log/index.d.ts.map +1 -1
  808. package/dist/esm/sent-email-log/index.js +15 -0
  809. package/dist/esm/sent-email-log/index.js.map +1 -1
  810. package/dist/esm/sent-email-log/sent-email-log-code.d.ts +7 -0
  811. package/dist/esm/sent-email-log/sent-email-log-code.d.ts.map +1 -1
  812. package/dist/esm/sent-email-log/sent-email-log-code.js +17 -3
  813. package/dist/esm/sent-email-log/sent-email-log-code.js.map +1 -1
  814. package/dist/esm/server-custom-node-call.d.ts +79 -0
  815. package/dist/esm/server-custom-node-call.d.ts.map +1 -0
  816. package/dist/esm/server-custom-node-call.js +278 -0
  817. package/dist/esm/server-custom-node-call.js.map +1 -0
  818. package/dist/esm/server-runtime-code.d.ts +39 -12
  819. package/dist/esm/server-runtime-code.d.ts.map +1 -1
  820. package/dist/esm/server-runtime-code.js +483 -40
  821. package/dist/esm/server-runtime-code.js.map +1 -1
  822. package/dist/esm/session-cookie-resolver.d.ts +3 -1
  823. package/dist/esm/session-cookie-resolver.d.ts.map +1 -1
  824. package/dist/esm/session-revocation-generator.d.ts +39 -0
  825. package/dist/esm/session-revocation-generator.d.ts.map +1 -0
  826. package/dist/esm/session-revocation-generator.js +172 -0
  827. package/dist/esm/session-revocation-generator.js.map +1 -0
  828. package/dist/esm/slack-message-values.d.ts +6 -0
  829. package/dist/esm/slack-message-values.d.ts.map +1 -0
  830. package/dist/esm/slack-message-values.js +68 -0
  831. package/dist/esm/slack-message-values.js.map +1 -0
  832. package/dist/esm/transactional-email-code.d.ts +11 -0
  833. package/dist/esm/transactional-email-code.d.ts.map +1 -1
  834. package/dist/esm/transactional-email-code.js +28 -4
  835. package/dist/esm/transactional-email-code.js.map +1 -1
  836. package/dist/esm/trigger-generator.d.ts +2 -0
  837. package/dist/esm/trigger-generator.d.ts.map +1 -1
  838. package/dist/esm/trigger-generator.js +15 -3
  839. package/dist/esm/trigger-generator.js.map +1 -1
  840. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  841. package/dist/esm/visitor-step-limits.d.ts +66 -0
  842. package/dist/esm/visitor-step-limits.d.ts.map +1 -0
  843. package/dist/esm/visitor-step-limits.js +181 -0
  844. package/dist/esm/visitor-step-limits.js.map +1 -0
  845. package/dist/esm/webhook-generator.d.ts.map +1 -1
  846. package/dist/esm/webhook-generator.js +93 -15
  847. package/dist/esm/webhook-generator.js.map +1 -1
  848. package/dist/esm/webhook-signature-verification.d.ts.map +1 -1
  849. package/dist/esm/webhook-signature-verification.js +13 -3
  850. package/dist/esm/webhook-signature-verification.js.map +1 -1
  851. package/dist/esm/workflow-auth-generator.d.ts +4 -1
  852. package/dist/esm/workflow-auth-generator.d.ts.map +1 -1
  853. package/dist/esm/workflow-auth-generator.js +8 -3
  854. package/dist/esm/workflow-auth-generator.js.map +1 -1
  855. package/dist/esm/workflow-component-plugin.js +12 -4
  856. package/dist/esm/workflow-component-plugin.js.map +1 -1
  857. package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
  858. package/dist/esm/workflow-project-plugin.js +306 -60
  859. package/dist/esm/workflow-project-plugin.js.map +1 -1
  860. package/dist/esm/workflow-scope.d.ts.map +1 -1
  861. package/dist/esm/workflow-scope.js +5 -0
  862. package/dist/esm/workflow-scope.js.map +1 -1
  863. package/package.json +4 -4
  864. package/src/account-delete-route-generator.ts +28 -4
  865. package/src/api-route-generator.ts +712 -134
  866. package/src/auth-generator.ts +1471 -142
  867. package/src/automations/activity-kicks.ts +139 -0
  868. package/src/automations/app-events-source.ts +117 -0
  869. package/src/automations/automations-project-plugin.ts +202 -0
  870. package/src/automations/automations-scope.ts +196 -0
  871. package/src/automations/dispatcher-source.ts +1072 -0
  872. package/src/automations/event-bus-source.ts +428 -0
  873. package/src/automations/handler-module-generator.ts +107 -0
  874. package/src/automations/hydrators-source.ts +647 -0
  875. package/src/booking-calendar-feed-route-generator.ts +8 -0
  876. package/src/configured-nextauth-url.ts +17 -0
  877. package/src/cron-request-verification.ts +55 -5
  878. package/src/data-api-route-generator.ts +780 -43
  879. package/src/ecommerce/parameterized-stock-chain.ts +52 -5
  880. package/src/ecommerce/place-order-guards.ts +785 -0
  881. package/src/ecommerce-customhandler-rewriter.ts +55 -2
  882. package/src/executor-generator.ts +372 -60
  883. package/src/graph-utils.ts +33 -0
  884. package/src/index.ts +70 -0
  885. package/src/invoice/api-routes-code.ts +20 -7
  886. package/src/invoice/index.ts +11 -6
  887. package/src/invoice/invoice-assembly-code.ts +8 -3
  888. package/src/invoice/pdf-generator-code.ts +2 -1
  889. package/src/invoice/pdf-service-client-code.ts +15 -7
  890. package/src/newsletter/admin-route-code.ts +319 -0
  891. package/src/newsletter/helper-code.ts +748 -0
  892. package/src/newsletter/newsletter-scope.ts +116 -0
  893. package/src/newsletter/project-plugin.ts +119 -0
  894. package/src/newsletter/routes-code.ts +186 -0
  895. package/src/nodes/account/account-compare-passwords.ts +17 -6
  896. package/src/nodes/account/account-delete-current.ts +1 -1
  897. package/src/nodes/account/account-get-current.ts +1 -1
  898. package/src/nodes/account/account-hash-password.ts +19 -3
  899. package/src/nodes/account/account-login.ts +24 -7
  900. package/src/nodes/account/account-logout.ts +1 -1
  901. package/src/nodes/account/account-refresh-session.ts +1 -1
  902. package/src/nodes/account/account-request-password-reset.ts +47 -0
  903. package/src/nodes/account/account-reset-password.ts +45 -0
  904. package/src/nodes/account/account-signup.ts +86 -20
  905. package/src/nodes/account/account-social-login.ts +1 -1
  906. package/src/nodes/ai/ai-custom-prompt.ts +25 -6
  907. package/src/nodes/ai/ai-detect-language.ts +25 -6
  908. package/src/nodes/ai/ai-generate-text-embedding.ts +16 -3
  909. package/src/nodes/ai/ai-provider-utils-sources.generated.ts +2 -2
  910. package/src/nodes/ai/ai-provider-utils.ts +135 -10
  911. package/src/nodes/ai/ai-select-database-data.ts +8 -3
  912. package/src/nodes/ai/ai-sentiment-analysis.ts +25 -6
  913. package/src/nodes/ai/ai-summarization.ts +25 -6
  914. package/src/nodes/ai/ai-text-classifier.ts +25 -6
  915. package/src/nodes/ai/ai-text-transform.ts +25 -6
  916. package/src/nodes/analytics/analytics-track-outcome.ts +111 -0
  917. package/src/nodes/cart/cart-price-order.ts +200 -2
  918. package/src/nodes/cost-bearing-node-types.ts +45 -0
  919. package/src/nodes/data/data-select.ts +11 -2
  920. package/src/nodes/email/email-mailersend.ts +16 -8
  921. package/src/nodes/email/email-mailgun.ts +17 -12
  922. package/src/nodes/email/email-postmark.ts +17 -12
  923. package/src/nodes/email/email-resend.ts +17 -12
  924. package/src/nodes/email/email-sendgrid.ts +17 -12
  925. package/src/nodes/email/html-escape-code.ts +21 -0
  926. package/src/nodes/email/message-trust.ts +198 -0
  927. package/src/nodes/email/outbound-message-guard.ts +572 -0
  928. package/src/nodes/email/trusted-html-code.ts +49 -0
  929. package/src/nodes/general/general-custom-js.ts +46 -7
  930. package/src/nodes/general/general-http-request.ts +449 -46
  931. package/src/nodes/general/general-rate-limiter.ts +54 -81
  932. package/src/nodes/general/general-wait.ts +80 -0
  933. package/src/nodes/general/visitor-request.ts +79 -0
  934. package/src/nodes/index.ts +12 -0
  935. package/src/nodes/integrations/integration-slack.ts +21 -6
  936. package/src/nodes/newsletter/newsletter-confirm.ts +44 -0
  937. package/src/nodes/newsletter/newsletter-subscribe.ts +78 -0
  938. package/src/nodes/newsletter/newsletter-unsubscribe.ts +43 -0
  939. package/src/nodes/realtime/realtime-join-channel.ts +5 -4
  940. package/src/nodes/realtime/realtime-send-channel-event.ts +13 -2
  941. package/src/nodes/realtime/realtime-send-channel-message.ts +13 -2
  942. package/src/nodes/sms/sms-infobip.ts +14 -4
  943. package/src/nodes/sms/sms-smsapi.ts +14 -4
  944. package/src/nodes/sms/sms-textmagic.ts +14 -4
  945. package/src/nodes/sms/sms-twilio.ts +14 -4
  946. package/src/nodes/types.ts +9 -1
  947. package/src/nodes/utility/utility-similarity-scoring.ts +33 -12
  948. package/src/page-access-guard-generator.ts +154 -0
  949. package/src/password-reset-route-generator.ts +531 -0
  950. package/src/payments/payment-core-code.ts +12 -7
  951. package/src/project-code-scanner.ts +98 -0
  952. package/src/public-step-guards.ts +448 -0
  953. package/src/publish-refusals.ts +459 -0
  954. package/src/realtime-generator.ts +457 -234
  955. package/src/runtime-storage-generator.ts +270 -11
  956. package/src/security-runtime/index.ts +53 -0
  957. package/src/security-runtime/security-runtime-code.ts +579 -0
  958. package/src/security-scanner.ts +253 -10
  959. package/src/segment-splitter.ts +10 -2
  960. package/src/segment-trust.ts +205 -2
  961. package/src/self-call-headers.ts +46 -0
  962. package/src/sent-email-log/index.ts +16 -0
  963. package/src/sent-email-log/sent-email-log-code.ts +30 -4
  964. package/src/server-custom-node-call.ts +331 -0
  965. package/src/server-runtime-code.ts +488 -40
  966. package/src/session-revocation-generator.ts +177 -0
  967. package/src/slack-message-values.ts +84 -0
  968. package/src/transactional-email-code.ts +30 -4
  969. package/src/trigger-generator.ts +19 -3
  970. package/src/visitor-step-limits.ts +228 -0
  971. package/src/webhook-generator.ts +94 -14
  972. package/src/webhook-signature-verification.ts +13 -3
  973. package/src/workflow-auth-generator.ts +9 -4
  974. package/src/workflow-component-plugin.ts +14 -4
  975. package/src/workflow-project-plugin.ts +374 -61
  976. package/src/workflow-scope.ts +6 -0
@@ -0,0 +1,1929 @@
1
+ import * as nodeCrypto from 'crypto'
2
+ import { EventEmitter } from 'events'
3
+ import * as net from 'net'
4
+ import { Readable } from 'stream'
5
+ import * as zlib from 'zlib'
6
+ import { generateSecurityRuntimeCode, securityRuntimeSpecifier } from '../src/security-runtime'
7
+ import { generateSharedRuntimeUtilsCode } from '../src/executor-generator'
8
+ import {
9
+ SEGMENT_PUBLIC_ERROR,
10
+ generateServerSegmentAPIRoute,
11
+ generateWebhookWorkflowAPIRoute,
12
+ } from '../src/api-route-generator'
13
+ import { buildSegmentTrust, SegmentTrust } from '../src/segment-trust'
14
+ import { collectMessageTrust } from '../src/nodes/email/message-trust'
15
+ import * as fs from 'fs'
16
+ import * as path from 'path'
17
+ import { utilitySimilarityScoring } from '../src/nodes/utility/utility-similarity-scoring'
18
+ import { loadServerRuntime } from './_helpers/load-server-runtime'
19
+ import { generateFillTemplateFn } from '../src/transactional-email-code'
20
+ import { resolveHandlerEntryName } from '../src/nodes/types'
21
+ import { generalHttpRequest } from '../src/nodes/general/general-http-request'
22
+ import { generalRateLimiter } from '../src/nodes/general/general-rate-limiter'
23
+ import { emailResend } from '../src/nodes/email/email-resend'
24
+ import { smsTwilio } from '../src/nodes/sms/sms-twilio'
25
+ import {
26
+ markVisitorBoundMessageNodes,
27
+ TOO_MANY_MESSAGES_MESSAGE,
28
+ MESSAGE_NOT_SENT_MESSAGE,
29
+ } from '../src/nodes/email/outbound-message-guard'
30
+ import {
31
+ AI_LIMIT_REACHED_MESSAGE,
32
+ AI_PROMPT_TOO_LONG_MESSAGE,
33
+ AI_SITE_CALLS_PER_DAY,
34
+ AI_NOT_CONFIGURED_MESSAGE,
35
+ AI_REFUSED_MESSAGE,
36
+ AI_UNAVAILABLE_MESSAGE,
37
+ generateAIProviderUtils,
38
+ generateAIStreamingProviderUtils,
39
+ } from '../src/nodes/ai/ai-provider-utils'
40
+ import { aiCustomPrompt } from '../src/nodes/ai/ai-custom-prompt'
41
+ import { aiGenerateTextEmbedding } from '../src/nodes/ai/ai-generate-text-embedding'
42
+ import type { WorkflowSegment } from '../src/types'
43
+
44
+ /**
45
+ * The workflow nodes that reach outside the site or spend the owner's money,
46
+ * EXECUTED from the source they emit: the HTTP request node (SSRF), email and
47
+ * SMS (visitor-supplied recipients, escaping, header line breaks), the AI
48
+ * provider call (per-site daily ceiling, prompt cap) and the rate limiter
49
+ * (on the shared durable limiter).
50
+ */
51
+
52
+ const SEGMENT_ROUTE_RUNTIME = securityRuntimeSpecifier('../../..')
53
+
54
+ const noop = (): void => undefined
55
+
56
+ const trust = (signedNodeIds: string[]): SegmentTrust =>
57
+ ({
58
+ signedNodeIds,
59
+ branchGates: [],
60
+ loopBodies: {},
61
+ previousFrom: null,
62
+ } as unknown as SegmentTrust)
63
+
64
+ /** One app instance's `utils/security-runtime.js`, with no database: it counts in memory. */
65
+ const loadSecurityRuntime = (): any => {
66
+ const module = { exports: {} as any }
67
+ const quiet = { warn: noop, error: noop, log: noop }
68
+ const requireShim = (id: string): unknown => {
69
+ if (id === 'crypto') {
70
+ return nodeCrypto
71
+ }
72
+ if (id === 'net') {
73
+ return net
74
+ }
75
+ throw new Error("Cannot find module '" + id + "'")
76
+ }
77
+ // eslint-disable-next-line no-new-func
78
+ new Function(
79
+ 'module',
80
+ 'exports',
81
+ 'require',
82
+ 'process',
83
+ 'console',
84
+ 'Buffer',
85
+ 'Date',
86
+ generateSecurityRuntimeCode()
87
+ )(module, module.exports, requireShim, { env: {} }, quiet, Buffer, Date)
88
+ return module.exports
89
+ }
90
+
91
+ /** A handler source evaluated the way a route inlines it, with `require` answered by `modules`. */
92
+ const loadHandler = (source: string, nodeType: string, modules: Record<string, unknown>): any => {
93
+ const entry = resolveHandlerEntryName(source.trim(), nodeType)
94
+ const requireShim = (id: string): unknown => {
95
+ if (id in modules) {
96
+ return modules[id]
97
+ }
98
+ throw new Error("Cannot find module '" + id + "'")
99
+ }
100
+ const quiet = { warn: noop, error: noop, log: noop }
101
+ // eslint-disable-next-line no-new-func
102
+ return new Function('require', 'console', `${source}\nreturn ${entry};`)(requireShim, quiet)
103
+ }
104
+
105
+ const wfCtx = (nodeId: string, refPath: string[]) => ({
106
+ type: 'workflowContext',
107
+ nodeId,
108
+ path: [nodeId, ...refPath],
109
+ })
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // general-http-request
113
+ // ---------------------------------------------------------------------------
114
+
115
+ interface Route {
116
+ status: number
117
+ headers?: Record<string, string>
118
+ body?: string
119
+ /** Bytes sent as they are (a compressed body). */
120
+ raw?: Buffer
121
+ /** The server never answers. */
122
+ hang?: boolean
123
+ }
124
+
125
+ /**
126
+ * A network the handler can only reach through `dns` and `http(s)`: a name
127
+ * resolves through `dnsTable`, and a connection (to the address the handler's
128
+ * own lookup returned) answers from `routes`. Every connection is recorded.
129
+ */
130
+ const fakeNetwork = (dnsTable: Record<string, string[]>, routes: Record<string, Route>) => {
131
+ const connections: Array<{ url: string; address: string }> = []
132
+ const lookups: string[] = []
133
+ const dns = {
134
+ lookup(hostname: string, options: any, callback: any) {
135
+ lookups.push(hostname)
136
+ const addresses = dnsTable[hostname]
137
+ if (!addresses) {
138
+ callback(
139
+ Object.assign(new Error('getaddrinfo ENOTFOUND ' + hostname), { code: 'ENOTFOUND' })
140
+ )
141
+ return
142
+ }
143
+ const list = addresses.map((address) => ({ address, family: net.isIP(address) }))
144
+ if (options && options.all) {
145
+ callback(null, list)
146
+ } else {
147
+ callback(null, list[0].address, list[0].family)
148
+ }
149
+ },
150
+ }
151
+ const transport = (protocol: string) => ({
152
+ request(options: any, onResponse: any) {
153
+ const req: any = new EventEmitter()
154
+ req.write = () => true
155
+ req.destroy = noop
156
+ req.end = () => {
157
+ const url = protocol + '//' + options.hostname + options.path
158
+ const connect = (address: string) => {
159
+ connections.push({ url, address })
160
+ const route = routes[url]
161
+ if (!route) {
162
+ req.emit('error', new Error('connect ECONNREFUSED ' + address))
163
+ return
164
+ }
165
+ if (route.hang) {
166
+ return
167
+ }
168
+ const res: any = Readable.from(
169
+ route.raw ? [route.raw] : route.body ? [Buffer.from(route.body)] : []
170
+ )
171
+ res.statusCode = route.status
172
+ res.statusMessage = route.status === 200 ? 'OK' : 'Found'
173
+ res.headers = route.headers || {}
174
+ onResponse(res)
175
+ }
176
+ if (net.isIP(options.hostname)) {
177
+ setImmediate(() => connect(options.hostname))
178
+ return
179
+ }
180
+ // What net.connect does with a `lookup` option: the socket goes to
181
+ // the address the lookup returns.
182
+ options.lookup(options.hostname, { all: true }, (err: any, list: any) => {
183
+ if (err) {
184
+ req.emit('error', err)
185
+ return
186
+ }
187
+ connect(list[0].address)
188
+ })
189
+ }
190
+ return req
191
+ },
192
+ })
193
+ const handler = loadHandler(generalHttpRequest.generateHandler(), 'general-http-request', {
194
+ net,
195
+ dns,
196
+ http: transport('http:'),
197
+ https: transport('https:'),
198
+ zlib,
199
+ })
200
+ return { handler, connections, lookups }
201
+ }
202
+
203
+ describe('general-http-request connects only to public addresses', () => {
204
+ const PUBLIC_IP = '93.184.216.34'
205
+
206
+ it.each([
207
+ ['loopback', 'http://127.0.0.1:3000/api/data/users'],
208
+ ['the cloud metadata service', 'http://169.254.169.254/latest/meta-data/iam/'],
209
+ ['IPv6 loopback', 'http://[::1]/'],
210
+ ['an IPv4-mapped loopback', 'http://[::ffff:127.0.0.1]/'],
211
+ ['a decimal-encoded loopback', 'http://2130706433/'],
212
+ ['a private network', 'http://10.1.2.3/'],
213
+ ['localhost', 'http://localhost:3000/'],
214
+ ['a metadata name', 'http://metadata.google.internal/computeMetadata/v1/'],
215
+ ['a non-http scheme', 'file:///etc/passwd'],
216
+ ['a unique-local IPv6 address', 'http://[fd00::1]/'],
217
+ ['a link-local IPv6 address', 'http://[fe80::1]/'],
218
+ ['an IPv6 multicast address', 'http://[ff02::1]/'],
219
+ ['an SIIT-mapped loopback', 'http://[::ffff:0:7f00:1]/'],
220
+ ])('refuses %s before connecting', async (_label, url) => {
221
+ const { handler, connections } = fakeNetwork({ localhost: ['127.0.0.1'] }, {})
222
+ const result = await handler({ url, method: 'GET' }, {})
223
+ expect(result).toMatchObject({ status: 0, body: null, error: 'The request URL is not allowed' })
224
+ expect(connections).toEqual([])
225
+ })
226
+
227
+ it('refuses a name that resolves to a private address', async () => {
228
+ const { handler, connections, lookups } = fakeNetwork(
229
+ { 'intranet.example.com': ['10.0.0.12'] },
230
+ { 'http://intranet.example.com/admin': { status: 200, body: 'secret' } }
231
+ )
232
+ const result = await handler({ url: 'http://intranet.example.com/admin' }, {})
233
+ expect(result.error).toBe('The request URL is not allowed')
234
+ expect(lookups).toEqual(['intranet.example.com'])
235
+ expect(connections).toEqual([])
236
+ })
237
+
238
+ it('refuses a name with any private address among its records', async () => {
239
+ const { handler, connections } = fakeNetwork(
240
+ { 'mixed.example.com': [PUBLIC_IP, '192.168.1.1'] },
241
+ { 'http://mixed.example.com/': { status: 200, body: 'x' } }
242
+ )
243
+ const result = await handler({ url: 'http://mixed.example.com/' }, {})
244
+ expect(result.error).toBe('The request URL is not allowed')
245
+ expect(connections).toEqual([])
246
+ })
247
+
248
+ it('refuses a public URL that redirects to a private one', async () => {
249
+ const { handler, connections } = fakeNetwork(
250
+ { 'api.example.com': [PUBLIC_IP] },
251
+ {
252
+ 'https://api.example.com/start': {
253
+ status: 302,
254
+ headers: { location: 'http://169.254.169.254/latest/meta-data/' },
255
+ },
256
+ 'http://169.254.169.254/latest/meta-data/': { status: 200, body: 'credentials' },
257
+ }
258
+ )
259
+ const result = await handler({ url: 'https://api.example.com/start' }, {})
260
+ expect(result.error).toBe('The request URL is not allowed')
261
+ expect(connections.map((c) => c.url)).toEqual(['https://api.example.com/start'])
262
+ })
263
+
264
+ it('refuses a redirect to a name that resolves to a private address', async () => {
265
+ const { handler, connections } = fakeNetwork(
266
+ { 'api.example.com': [PUBLIC_IP], 'internal-db.example.com': ['172.20.0.5'] },
267
+ {
268
+ 'https://api.example.com/start': {
269
+ status: 307,
270
+ headers: { location: 'https://internal-db.example.com/' },
271
+ },
272
+ }
273
+ )
274
+ const result = await handler({ url: 'https://api.example.com/start' }, {})
275
+ expect(result.error).toBe('The request URL is not allowed')
276
+ expect(connections).toHaveLength(1)
277
+ })
278
+
279
+ it('follows a redirect to another public host and returns its JSON body', async () => {
280
+ const { handler, connections } = fakeNetwork(
281
+ { 'api.example.com': [PUBLIC_IP], 'cdn.example.net': ['151.101.1.1'] },
282
+ {
283
+ 'https://api.example.com/v1/items?page=2': {
284
+ status: 301,
285
+ headers: { location: 'https://cdn.example.net/v1/items?page=2' },
286
+ },
287
+ 'https://cdn.example.net/v1/items?page=2': {
288
+ status: 200,
289
+ headers: { 'content-type': 'application/json; charset=utf-8' },
290
+ body: '{"items":[1,2]}',
291
+ },
292
+ }
293
+ )
294
+ const result = await handler(
295
+ {
296
+ url: 'https://api.example.com/v1/items',
297
+ queryParams: [{ key: 'page', value: '2' }],
298
+ },
299
+ {}
300
+ )
301
+ expect(result).toEqual({
302
+ status: 200,
303
+ statusText: 'OK',
304
+ headers: { 'content-type': 'application/json; charset=utf-8' },
305
+ body: { items: [1, 2] },
306
+ })
307
+ expect(connections.map((c) => c.address)).toEqual([PUBLIC_IP, '151.101.1.1'])
308
+ })
309
+
310
+ it('stops after 3 redirects', async () => {
311
+ const routes: Record<string, Route> = {}
312
+ for (let i = 0; i < 5; i++) {
313
+ routes['https://api.example.com/' + i] = {
314
+ status: 302,
315
+ headers: { location: '/' + (i + 1) },
316
+ }
317
+ }
318
+ const { handler, connections } = fakeNetwork({ 'api.example.com': [PUBLIC_IP] }, routes)
319
+ const result = await handler({ url: 'https://api.example.com/0' }, {})
320
+ expect(result.statusText).toBe('Too Many Redirects')
321
+ expect(connections).toHaveLength(4)
322
+ })
323
+
324
+ it('refuses a response larger than 2 MB', async () => {
325
+ const { handler } = fakeNetwork(
326
+ { 'files.example.com': [PUBLIC_IP] },
327
+ { 'https://files.example.com/big': { status: 200, body: 'x'.repeat(2 * 1024 * 1024 + 1) } }
328
+ )
329
+ const result = await handler({ url: 'https://files.example.com/big' }, {})
330
+ expect(result).toMatchObject({ status: 0, error: 'The response is larger than 2 MB' })
331
+ })
332
+
333
+ it('refuses a compressed response that inflates past 2 MB', async () => {
334
+ const { handler } = fakeNetwork(
335
+ { 'files.example.com': [PUBLIC_IP] },
336
+ {
337
+ 'https://files.example.com/bomb': {
338
+ status: 200,
339
+ headers: { 'content-encoding': 'gzip' },
340
+ raw: zlib.gzipSync(Buffer.alloc(50 * 1024 * 1024)),
341
+ },
342
+ }
343
+ )
344
+ const result = await handler({ url: 'https://files.example.com/bomb' }, {})
345
+ expect(result).toMatchObject({ status: 0, error: 'The response is larger than 2 MB' })
346
+ })
347
+
348
+ it('refuses names that resolve to IPv6 unique-local or link-local addresses', async () => {
349
+ const { handler, connections } = fakeNetwork(
350
+ { 'ula.example.com': ['fd12:3456::5'], 'll.example.com': ['fe80::2'] },
351
+ {}
352
+ )
353
+ expect((await handler({ url: 'http://ula.example.com/' }, {})).error).toBe(
354
+ 'The request URL is not allowed'
355
+ )
356
+ expect((await handler({ url: 'http://ll.example.com/' }, {})).error).toBe(
357
+ 'The request URL is not allowed'
358
+ )
359
+ expect(connections).toEqual([])
360
+ })
361
+
362
+ it('gives up when the server does not answer in time', async () => {
363
+ const { handler } = fakeNetwork(
364
+ { 'slow.example.com': [PUBLIC_IP] },
365
+ { 'https://slow.example.com/': { status: 200, hang: true } }
366
+ )
367
+ const result = await handler({ url: 'https://slow.example.com/', timeout: 50 }, {})
368
+ expect(result).toMatchObject({ status: 0, statusText: 'Request Timeout' })
369
+ })
370
+ })
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // Email bodies and subjects: values escaped where the template is filled
374
+ // ---------------------------------------------------------------------------
375
+
376
+ const loadResolveConfig = (): ((config: any, context: any) => any) => {
377
+ const module = { exports: {} as any }
378
+ // eslint-disable-next-line no-new-func
379
+ new Function('module', 'exports', 'require', 'process', generateSharedRuntimeUtilsCode())(
380
+ module,
381
+ module.exports,
382
+ () => ({}),
383
+ { env: {} }
384
+ )
385
+ return module.exports.resolveConfig
386
+ }
387
+
388
+ describe('email templates keep the author HTML and escape every value', () => {
389
+ const resolveConfig = loadResolveConfig()
390
+ const context = {
391
+ form: {
392
+ name: '<a href="https://evil.example">Verify your account</a>',
393
+ topic: 'Hello\r\nBcc: everyone@example.com',
394
+ },
395
+ }
396
+
397
+ it('escapes a visitor value in the body and keeps the template markup', () => {
398
+ const resolved = resolveConfig(
399
+ {
400
+ to: 'owner@shop.example',
401
+ subject: 'New message: {{topic}}',
402
+ body: '<p>From <strong>{{name}}</strong></p><a href="{{link}}">Open</a>',
403
+ templateParams: [
404
+ { key: 'name', value: wfCtx('form', ['name']) },
405
+ { key: 'topic', value: wfCtx('form', ['topic']) },
406
+ { key: 'link', value: 'https://shop.example/inbox?a=1&b=2' },
407
+ ],
408
+ },
409
+ context
410
+ )
411
+ expect(resolved.body).toBe(
412
+ '<p>From <strong>&lt;a href=&quot;https://evil.example&quot;&gt;Verify your account&lt;/a&gt;</strong></p>' +
413
+ '<a href="https://shop.example/inbox?a=1&amp;b=2">Open</a>'
414
+ )
415
+ expect(resolved.subject).toBe('New message: Hello Bcc: everyone@example.com')
416
+ expect(resolved.subject).not.toMatch(/[\r\n]/)
417
+ })
418
+
419
+ const chip =
420
+ '<span class="context-value-inline" data-ctx-node-id="form" ' +
421
+ 'data-ctx-path=\'["form","name"]\'>Name</span>'
422
+
423
+ it('escapes a rich-text placeholder value in a body (a text node: quotes stay)', () => {
424
+ const resolved = resolveConfig({ body: '<p>Hi ' + chip + '</p>' }, context)
425
+ expect(resolved.body).toBe(
426
+ '<p>Hi &lt;a href="https://evil.example"&gt;Verify your account&lt;/a&gt;</p>'
427
+ )
428
+ })
429
+
430
+ it('puts a placeholder value in a subject or an SMS message as text: no entities, no line breaks', () => {
431
+ const resolved = resolveConfig(
432
+ { subject: 'Hi ' + chip, message: 'Hi ' + chip },
433
+ { form: { name: "O'Brien & Co\r\nBcc: list@example.com" } }
434
+ )
435
+ expect(resolved.subject).toBe("Hi O'Brien & Co Bcc: list@example.com")
436
+ expect(resolved.message).toBe("Hi O'Brien & Co Bcc: list@example.com")
437
+ })
438
+
439
+ it('escapes once: an already escaped value, a tracking link and a visitor &lt; stay text', () => {
440
+ const resolved = resolveConfig(
441
+ {
442
+ subject: 'Order from {{shop}}',
443
+ body: '<p>{{shop}} / {{raw}}</p><a href="{{track}}">Track</a><p>{{note}}</p>',
444
+ templateParams: [
445
+ // What the lifecycle token scripts hand over: already escaped.
446
+ { key: 'shop', value: 'Joe&#39;s Coffee &amp; Co' },
447
+ { key: 'raw', value: "Joe's" },
448
+ { key: 'track', value: 'https://track.example/?n=1Z9&amp;lang=en&x=1' },
449
+ { key: 'note', value: '&lt;script>alert(1)</script>' },
450
+ ],
451
+ },
452
+ {}
453
+ )
454
+ expect(resolved.body).toBe(
455
+ '<p>Joe&#39;s Coffee &amp; Co / Joe&#39;s</p>' +
456
+ '<a href="https://track.example/?n=1Z9&amp;lang=en&amp;x=1">Track</a>' +
457
+ '<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>'
458
+ )
459
+ // A subject is text: an escaped value reads as its characters.
460
+ expect(resolved.subject).toBe("Order from Joe's Coffee & Co")
461
+ })
462
+
463
+ it('fills the account emails the same way: body escaped, subject without line breaks', () => {
464
+ // eslint-disable-next-line no-new-func
465
+ const fill = new Function(
466
+ `${generateFillTemplateFn()}\nreturn { fillTemplate: fillTemplate, fillSubjectTemplate: fillSubjectTemplate };`
467
+ )()
468
+ const values = { userName: '<img src=x onerror=alert(1)>\r\nX-Header: 1', siteName: 'Shop' }
469
+ expect(fill.fillTemplate('<p>Hi {{userName}}, welcome to {{siteName}}</p>', values)).toBe(
470
+ '<p>Hi &lt;img src=x onerror=alert(1)&gt;\r\nX-Header: 1, welcome to Shop</p>'
471
+ )
472
+ expect(fill.fillSubjectTemplate('Welcome {{userName}}', values)).toBe(
473
+ 'Welcome <img src=x onerror=alert(1)> X-Header: 1'
474
+ )
475
+ })
476
+
477
+ describe('the products list of a platform email', () => {
478
+ // The withdrawal / abandoned-cart fallback body: {{productsList}} must show
479
+ // the list, not its markup as text.
480
+ const body = '<div style="padding:16px;">{{productsList}}</div><p>Hi {{customerName}}</p>'
481
+ const lines = [
482
+ { product_name: '<img src=x onerror=alert(1)>Mug', quantity: '2' },
483
+ { product_name: 'Tee', quantity: 1, configuration_label: 'Size <b>L</b>' },
484
+ ]
485
+
486
+ it('is built on the server from its lines, as HTML, with every field escaped', () => {
487
+ const resolved = resolveConfig(
488
+ {
489
+ body,
490
+ templateParams: [
491
+ { key: 'products', value: wfCtx('assemble', ['selectedItems']) },
492
+ // What the browser handed over is never used: it may be anything.
493
+ { key: 'productsList', value: wfCtx('assemble', ['productsListHtml']) },
494
+ { key: 'customerName', value: wfCtx('assemble', ['customerName']) },
495
+ ],
496
+ },
497
+ {
498
+ assemble: {
499
+ selectedItems: lines,
500
+ productsListHtml: '<a href="https://evil.example">Refund here</a>',
501
+ customerName: 'Ann',
502
+ },
503
+ }
504
+ )
505
+ expect(resolved.body).toBe(
506
+ '<div style="padding:16px;"><ul style="margin:0;padding-left:18px;">' +
507
+ '<li>2× <strong>&lt;img src=x onerror=alert(1)&gt;Mug</strong></li>' +
508
+ '<li>1× <strong>Tee</strong> — Size &lt;b&gt;L&lt;/b&gt;</li>' +
509
+ '</ul></div><p>Hi Ann</p>'
510
+ )
511
+ expect(resolved.body).not.toContain('evil.example')
512
+ })
513
+
514
+ it('escapes a visitor value named productsList when the server built no list', () => {
515
+ const resolved = resolveConfig(
516
+ {
517
+ body,
518
+ templateParams: [
519
+ { key: 'productsList', value: wfCtx('form', ['productsList']) },
520
+ // A list-looking object from the request is not trusted either.
521
+ { key: 'customerName', value: wfCtx('form', ['forged']) },
522
+ ],
523
+ },
524
+ {
525
+ form: {
526
+ productsList: '<ul><li><a href="https://evil.example">x</a></li></ul>',
527
+ forged: { __html: '<b>x</b>' },
528
+ },
529
+ }
530
+ )
531
+ expect(resolved.body).toBe(
532
+ '<div style="padding:16px;">&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://evil.example&quot;&gt;x&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</div>' +
533
+ '<p>Hi [object Object]</p>'
534
+ )
535
+ })
536
+ })
537
+ })
538
+
539
+ // ---------------------------------------------------------------------------
540
+ // Email / SMS sends to visitor-supplied recipients
541
+ // ---------------------------------------------------------------------------
542
+
543
+ const loadEmailNode = (security: unknown, withRuntime = true) => {
544
+ const sent: any[] = []
545
+ class Resend {
546
+ emails = {
547
+ send: async (payload: any) => {
548
+ sent.push(payload)
549
+ return { data: { id: 'msg_' + sent.length } }
550
+ },
551
+ }
552
+ }
553
+ const source = withRuntime
554
+ ? emailResend.generateServerHandler!({})
555
+ : emailResend.generateHandler()
556
+ const handler = loadHandler(source, 'email-resend', {
557
+ resend: { Resend },
558
+ [SEGMENT_ROUTE_RUNTIME]: security,
559
+ })
560
+ return { handler, sent }
561
+ }
562
+
563
+ const visitor = (remoteAddress: string, internal = false) => ({
564
+ __request: { headers: {}, remoteAddress, internal },
565
+ })
566
+
567
+ describe('email nodes', () => {
568
+ const autoReply = (to: string) => ({
569
+ apiKey: 're_test',
570
+ from: 'Shop <hello@shop.example>',
571
+ to,
572
+ subject: 'Thanks for your message',
573
+ body: '<p>We got it.</p>',
574
+ __visitorRecipientFields: ['to'],
575
+ })
576
+
577
+ it('takes line breaks out of every header field', async () => {
578
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
579
+ const result = await handler(
580
+ {
581
+ apiKey: 're_test',
582
+ from: 'hello@shop.example',
583
+ to: 'owner@shop.example\r\nBcc: list@example.com',
584
+ replyTo: 'visitor@example.com\nX-Injected: 1',
585
+ subject: 'Order\r\n confirmed',
586
+ body: '<p>Body\nkept</p>',
587
+ },
588
+ visitor('203.0.113.7')
589
+ )
590
+ expect(result.success).toBe(true)
591
+ expect(sent[0].to).toEqual(['owner@shop.example Bcc: list@example.com'])
592
+ expect(sent[0].replyTo).toBe('visitor@example.com X-Injected: 1')
593
+ expect(sent[0].subject).toBe('Order confirmed')
594
+ expect(sent[0].html).toBe('<p>Body\nkept</p>')
595
+ })
596
+
597
+ it('sends a visitor-supplied recipient at most 3 times an hour', async () => {
598
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
599
+ const results = []
600
+ for (let i = 1; i <= 4; i++) {
601
+ results.push(await handler(autoReply('Victim@Example.com'), visitor('198.51.100.' + i)))
602
+ }
603
+ expect(results.map((r) => r.success)).toEqual([true, true, true, undefined])
604
+ // Skipped, not failed: the visitor's action goes on.
605
+ expect(results[3]).toEqual({ id: '', skipped: true, reason: TOO_MANY_MESSAGES_MESSAGE })
606
+ expect(sent).toHaveLength(3)
607
+ })
608
+
609
+ it('counts every address of a list against the recipient cap', async () => {
610
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
611
+ for (let i = 1; i <= 3; i++) {
612
+ await handler(autoReply('victim@example.com'), visitor('198.51.100.' + i))
613
+ }
614
+ const result = await handler(
615
+ autoReply('fresh@example.com, VICTIM@example.com'),
616
+ visitor('198.51.100.50')
617
+ )
618
+ expect(result.reason).toBe(TOO_MANY_MESSAGES_MESSAGE)
619
+ expect(sent).toHaveLength(3)
620
+ })
621
+
622
+ it('lets one visitor send to at most 5 visitor-supplied addresses an hour', async () => {
623
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
624
+ const results = []
625
+ for (let i = 1; i <= 6; i++) {
626
+ results.push(
627
+ await handler(autoReply('person' + i + '@example.com'), visitor('2001:db8:1:2::' + i))
628
+ )
629
+ }
630
+ // Every address in one /64 is one visitor.
631
+ expect(results.map((r) => r.success)).toEqual([true, true, true, true, true, undefined])
632
+ expect(sent).toHaveLength(5)
633
+ })
634
+
635
+ it('never limits a recipient the author fixed (a contact form to the owner)', async () => {
636
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
637
+ for (let i = 0; i < 10; i++) {
638
+ const result = await handler(
639
+ { ...autoReply('owner@shop.example'), __visitorRecipientFields: undefined },
640
+ visitor('198.51.100.9')
641
+ )
642
+ expect(result.success).toBe(true)
643
+ }
644
+ expect(sent).toHaveLength(10)
645
+ })
646
+
647
+ it("does not count this deployment's own server calls", async () => {
648
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
649
+ for (let i = 0; i < 5; i++) {
650
+ await handler(autoReply('buyer@example.com'), visitor('10.0.0.2', true))
651
+ }
652
+ expect(sent).toHaveLength(5)
653
+ })
654
+
655
+ it('sends a value body as text when it is marked', async () => {
656
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
657
+ await handler(
658
+ {
659
+ ...autoReply('friend@example.com'),
660
+ body: '<a href="https://evil.example">Claim your prize</a>',
661
+ __escapeBody: true,
662
+ },
663
+ visitor('198.51.100.20')
664
+ )
665
+ expect(sent[0].html).toBe(
666
+ '&lt;a href=&quot;https://evil.example&quot;&gt;Claim your prize&lt;/a&gt;'
667
+ )
668
+ })
669
+
670
+ it('refuses a visitor-supplied recipient when the security runtime is missing', async () => {
671
+ const { handler, sent } = loadEmailNode(null, false)
672
+ const refused = await handler(autoReply('victim@example.com'), visitor('198.51.100.30'))
673
+ expect(refused).toEqual({ id: '', success: false, error: MESSAGE_NOT_SENT_MESSAGE })
674
+ const fixed = await handler(
675
+ { ...autoReply('owner@shop.example'), __visitorRecipientFields: undefined },
676
+ visitor('198.51.100.30')
677
+ )
678
+ expect(fixed.success).toBe(true)
679
+ expect(sent).toHaveLength(1)
680
+ })
681
+
682
+ it('caps SMS to a visitor-supplied number the same way', async () => {
683
+ const sent: any[] = []
684
+ const twilio = () => ({
685
+ messages: {
686
+ create: async (payload: any) => {
687
+ sent.push(payload)
688
+ return { sid: 'SM' + sent.length, status: 'queued' }
689
+ },
690
+ },
691
+ })
692
+ const handler = loadHandler(smsTwilio.generateServerHandler!({}), 'sms-twilio', {
693
+ twilio,
694
+ [SEGMENT_ROUTE_RUNTIME]: loadSecurityRuntime(),
695
+ })
696
+ const config = {
697
+ accountSid: 'AC1',
698
+ authToken: 't',
699
+ from: '+15550000000',
700
+ to: '+1 (555) 123-4567',
701
+ message: 'Your booking is confirmed',
702
+ __visitorRecipientFields: ['to'],
703
+ }
704
+ const results = []
705
+ for (let i = 1; i <= 4; i++) {
706
+ results.push(await handler({ ...config }, visitor('198.51.100.' + i)))
707
+ }
708
+ expect(results.map((r) => r.success)).toEqual([true, true, true, undefined])
709
+ expect(sent).toHaveLength(3)
710
+ })
711
+ })
712
+
713
+ describe('which recipients the route marks as visitor-supplied', () => {
714
+ const emailNode = (id: string, config: Record<string, unknown>) => ({
715
+ id,
716
+ type: 'email-resend',
717
+ config,
718
+ })
719
+ const segment = (nodes: any[]): WorkflowSegment =>
720
+ ({ id: 'seg', env: 'server', nodeIds: nodes.map((n) => n.id), nodes, edges: [] } as any)
721
+
722
+ it('marks a recipient bound to a browser node, page state or a token', () => {
723
+ const marked = markVisitorBoundMessageNodes(
724
+ segment([
725
+ emailNode('send', {
726
+ to: wfCtx('extractForm', ['email']),
727
+ cc: '{{state.copyTo}}',
728
+ bcc: 'owner@shop.example',
729
+ }),
730
+ ]),
731
+ trust([]),
732
+ undefined
733
+ )
734
+ expect((marked.nodes[0].config as any).__visitorRecipientFields).toEqual(['to', 'cc'])
735
+ })
736
+
737
+ it("leaves a recipient read from the owner's settings unmarked, and the segment untouched", () => {
738
+ const settings = { id: 'settings', type: 'ecommerce-get-settings', config: {} }
739
+ const input = segment([
740
+ settings,
741
+ emailNode('send', { to: wfCtx('settings', ['notificationEmail']) }),
742
+ emailNode('notify', { to: wfCtx('ownerPayload', ['recipients']), body: '<p>x</p>' }),
743
+ ])
744
+ const marked = markVisitorBoundMessageNodes(
745
+ input,
746
+ {
747
+ ...trust([]),
748
+ messageTrust: {
749
+ ownerRecipientNodeIds: ['settings', 'ownerPayload'],
750
+ trustedHtmlNodeIds: [],
751
+ },
752
+ } as SegmentTrust,
753
+ undefined
754
+ )
755
+ expect(marked).toBe(input)
756
+ })
757
+
758
+ it('counts an address read from any other row (the visitor may just have written it)', () => {
759
+ const marked = markVisitorBoundMessageNodes(
760
+ segment([
761
+ { id: 'lookup', type: 'data-select', config: { tableName: 'orders' } },
762
+ emailNode('send', { to: wfCtx('lookup', ['rows', '0', 'email']), body: '<p>Thanks</p>' }),
763
+ ]),
764
+ undefined,
765
+ undefined
766
+ )
767
+ expect((marked.nodes[1].config as any).__visitorRecipientFields).toEqual(['to'])
768
+ })
769
+
770
+ it('leaves a route only an admin or the server itself can call untouched', () => {
771
+ const input = segment([emailNode('send', { to: wfCtx('extractForm', ['email']) })])
772
+ expect(
773
+ markVisitorBoundMessageNodes(input, undefined, {
774
+ requiresAuth: true,
775
+ allowedRoles: ['admin'],
776
+ derivedFrom: 'page',
777
+ })
778
+ ).toBe(input)
779
+ expect(
780
+ markVisitorBoundMessageNodes(input, undefined, {
781
+ requiresAuth: false,
782
+ allowedRoles: [],
783
+ serverOnly: true,
784
+ derivedFrom: 'server-callers',
785
+ })
786
+ ).toBe(input)
787
+ const anyUser = markVisitorBoundMessageNodes(input, undefined, {
788
+ requiresAuth: true,
789
+ allowedRoles: [],
790
+ derivedFrom: 'page',
791
+ })
792
+ expect((anyUser.nodes[0].config as any).__visitorRecipientFields).toEqual(['to'])
793
+ })
794
+
795
+ it("never counts the signed-in user's own address, read from the session", () => {
796
+ const session = {
797
+ ...trust([]),
798
+ session: { identityNodes: { me: 'account-get-current' }, identityReads: {} },
799
+ } as unknown as SegmentTrust
800
+ const ownAddress = segment([
801
+ emailNode('token', { to: '{{Current User.email}}', body: '<p>Your receipt</p>' }),
802
+ emailNode('reader', { to: wfCtx('me', ['email']) }),
803
+ { id: 'text', type: 'sms-twilio', config: { to: '{{ currentUser.phone }}' } },
804
+ ])
805
+ expect(markVisitorBoundMessageNodes(ownAddress, session, undefined)).toBe(ownAddress)
806
+
807
+ // Where the route binds no session the token is not the session's: counted.
808
+ const unbound = markVisitorBoundMessageNodes(ownAddress, trust([]), undefined)
809
+ expect(unbound.nodes.map((n) => (n.config as any).__visitorRecipientFields)).toEqual([
810
+ ['to'],
811
+ ['to'],
812
+ ['to'],
813
+ ])
814
+ // A value the request supplies is still counted on a session-bound route.
815
+ const fromRequest = markVisitorBoundMessageNodes(
816
+ segment([
817
+ emailNode('send', { to: wfCtx('extractForm', ['email']) }),
818
+ emailNode('state', { to: '{{state.currentUser.email}}' }),
819
+ ]),
820
+ session,
821
+ undefined
822
+ )
823
+ expect(fromRequest.nodes.map((n) => (n.config as any).__visitorRecipientFields)).toEqual([
824
+ ['to'],
825
+ ['to'],
826
+ ])
827
+ })
828
+
829
+ it('marks a body that is a visitor value, not a template', () => {
830
+ const marked = markVisitorBoundMessageNodes(
831
+ segment([
832
+ emailNode('send', { to: 'owner@shop.example', body: wfCtx('extractForm', ['message']) }),
833
+ ]),
834
+ undefined,
835
+ undefined
836
+ )
837
+ expect(marked.nodes[0].config).toMatchObject({ __escapeBody: true })
838
+ expect((marked.nodes[0].config as any).__visitorRecipientFields).toBeUndefined()
839
+ })
840
+
841
+ it('wires the mark and the visitor request into the generated segment route', () => {
842
+ const route = generateServerSegmentAPIRoute(
843
+ segment([emailNode('send', { to: wfCtx('extractForm', ['email']), body: '<p>Thanks</p>' })]),
844
+ 'Contact'
845
+ )
846
+ expect(route).toMatch(/"__visitorRecipientFields": \[\s*"to"\s*\]/)
847
+ expect(route).toContain(
848
+ "context.__request = { headers: req.headers || {}, remoteAddress: (req.socket && req.socket.remoteAddress) || '', internal: utils.isInternalCall(req) };"
849
+ )
850
+ expect(route).toContain(`require('${SEGMENT_ROUTE_RUNTIME}')`)
851
+ expect(route).not.toContain('__getClientIp')
852
+
853
+ const plain = generateServerSegmentAPIRoute(
854
+ segment([{ id: 'read', type: 'data-select', config: { tableName: 'posts' } }]),
855
+ 'Read'
856
+ )
857
+ expect(plain).not.toContain('context.__request = ')
858
+ })
859
+ })
860
+
861
+ // ---------------------------------------------------------------------------
862
+ // AI: per-site daily ceiling and prompt cap in __ai_callProvider
863
+ // ---------------------------------------------------------------------------
864
+
865
+ const fakeOpenAI = () => {
866
+ const calls: any[] = []
867
+ class OpenAI {
868
+ chat = {
869
+ completions: {
870
+ create: async (opts: any) => {
871
+ calls.push(opts)
872
+ if (opts.stream) {
873
+ return (async function* () {
874
+ yield { choices: [{ delta: { content: 'hi' } }] }
875
+ })()
876
+ }
877
+ return { choices: [{ message: { content: 'ok' } }], usage: { total_tokens: 2 } }
878
+ },
879
+ },
880
+ }
881
+ embeddings = {
882
+ create: async (opts: any) => {
883
+ calls.push(opts)
884
+ return { data: [{ embedding: [0.1, 0.2] }], model: opts.model, usage: {} }
885
+ },
886
+ }
887
+ }
888
+ return { module: { default: OpenAI }, calls }
889
+ }
890
+
891
+ const loadAIUtils = (security: unknown, relativePrefix: string | undefined = '../../..') => {
892
+ const openai = fakeOpenAI()
893
+ const source = generateAIStreamingProviderUtils(relativePrefix)
894
+ const requireShim = (id: string): unknown => {
895
+ if (id === 'openai') {
896
+ return openai.module
897
+ }
898
+ if (id === SEGMENT_ROUTE_RUNTIME) {
899
+ return security
900
+ }
901
+ throw new Error("Cannot find module '" + id + "'")
902
+ }
903
+ const quiet = { warn: noop, error: noop, log: noop }
904
+ // eslint-disable-next-line no-new-func
905
+ const utils = new Function(
906
+ 'require',
907
+ 'console',
908
+ `${source}\nreturn { call: __ai_callProvider, stream: __ai_callProviderStreaming };`
909
+ )(requireShim, quiet)
910
+ return { ...utils, calls: openai.calls }
911
+ }
912
+
913
+ describe('AI calls share one per-site daily ceiling', () => {
914
+ const params = (userMessage: string) => ({
915
+ provider: 'openai',
916
+ model: 'gpt-4o',
917
+ token: 'sk-test',
918
+ userMessage,
919
+ temperature: 0.7,
920
+ maxTokens: 50,
921
+ })
922
+
923
+ it(`refuses the call after ${AI_SITE_CALLS_PER_DAY} in a day, before the provider is reached`, async () => {
924
+ const { call, stream, calls } = loadAIUtils(loadSecurityRuntime())
925
+ for (let i = 0; i < AI_SITE_CALLS_PER_DAY; i++) {
926
+ await call(params('question ' + i))
927
+ }
928
+ expect(calls).toHaveLength(AI_SITE_CALLS_PER_DAY)
929
+ await expect(call(params('one more'))).rejects.toThrow(AI_LIMIT_REACHED_MESSAGE)
930
+ // The streaming path counts against the same budget.
931
+ await expect(stream(params('one more'), async (): Promise<void> => undefined)).rejects.toThrow(
932
+ AI_LIMIT_REACHED_MESSAGE
933
+ )
934
+ expect(calls).toHaveLength(AI_SITE_CALLS_PER_DAY)
935
+ })
936
+
937
+ it('refuses a prompt over 8,000 characters without counting or sending it', async () => {
938
+ const rateLimit = jest.fn(async () => ({ allowed: true, retryAfter: 0 }))
939
+ const { call, calls } = loadAIUtils({ rateLimit })
940
+ await expect(call(params('x'.repeat(8001)))).rejects.toThrow(AI_PROMPT_TOO_LONG_MESSAGE)
941
+ expect(rateLimit).not.toHaveBeenCalled()
942
+ await call(params('x'.repeat(8000)))
943
+ expect(rateLimit).toHaveBeenCalledWith({
944
+ key: 'site:ai-calls',
945
+ limit: 2000,
946
+ windowSeconds: 86400,
947
+ })
948
+ expect(calls).toHaveLength(1)
949
+ })
950
+
951
+ it('refuses every call where the security runtime is missing', async () => {
952
+ const { call, calls } = loadAIUtils(null, undefined)
953
+ await expect(call(params('hello'))).rejects.toThrow(AI_UNAVAILABLE_MESSAGE)
954
+ expect(calls).toHaveLength(0)
955
+ expect(generateAIProviderUtils()).not.toContain('security-runtime')
956
+ })
957
+
958
+ it('never lets a provider or secret detail leave the server', async () => {
959
+ const logged: string[] = []
960
+ const log = {
961
+ warn: noop,
962
+ error: (...args: unknown[]) => logged.push(args.join(' ')),
963
+ log: noop,
964
+ }
965
+ class RefusingOpenAI {
966
+ chat = {
967
+ completions: {
968
+ create: async () => {
969
+ throw new Error('401 Incorrect API key provided: OPENAI_A***_KEY')
970
+ },
971
+ },
972
+ }
973
+ }
974
+ const allowing = { rateLimit: async () => ({ allowed: true, retryAfter: 0 }) }
975
+ const source = aiCustomPrompt.generateServerHandler!({})
976
+ const requireShim = (id: string): unknown =>
977
+ id === 'openai'
978
+ ? { default: RefusingOpenAI }
979
+ : id === SEGMENT_ROUTE_RUNTIME
980
+ ? allowing
981
+ : undefined
982
+ // eslint-disable-next-line no-new-func
983
+ const prompt = new Function(
984
+ 'require',
985
+ 'console',
986
+ 'process',
987
+ `${source}\nreturn ${resolveHandlerEntryName(source, 'ai-custom-prompt')};`
988
+ )(requireShim, log, { env: {} })
989
+
990
+ // A token set to an env var's NAME is sent as the key, and the provider quotes it.
991
+ const refused = await prompt({ prompt: 'Hi', token: 'OPENAI_API_KEY' }, {})
992
+ expect(refused).toEqual({
993
+ error: true,
994
+ message: AI_REFUSED_MESSAGE,
995
+ code: 'provider_error',
996
+ })
997
+ // A secret that is not set.
998
+ const missing = await prompt({ prompt: 'Hi', token: 'teleporthq.secrets.SHOP_OPENAI_KEY' }, {})
999
+ expect(missing).toEqual({
1000
+ error: true,
1001
+ message: AI_NOT_CONFIGURED_MESSAGE,
1002
+ code: 'authentication_error',
1003
+ })
1004
+ expect(JSON.stringify([refused, missing])).not.toMatch(/OPENAI|SHOP_OPENAI_KEY|401/)
1005
+ // The detail is in the server log.
1006
+ expect(logged.join('\n')).toContain('OPENAI_A***_KEY')
1007
+ expect(logged.join('\n')).toContain('SHOP_OPENAI_KEY')
1008
+ })
1009
+
1010
+ it('answers a refused node with a clean error, and an optional embedding degrades', async () => {
1011
+ const refusing = { rateLimit: async () => ({ allowed: false, retryAfter: 3600 }) }
1012
+ const openai = fakeOpenAI()
1013
+ const prompt = loadHandler(aiCustomPrompt.generateServerHandler!({}), 'ai-custom-prompt', {
1014
+ openai: openai.module,
1015
+ [SEGMENT_ROUTE_RUNTIME]: refusing,
1016
+ })
1017
+ expect(await prompt({ prompt: 'Hi', token: 'sk-test' }, {})).toEqual({
1018
+ error: true,
1019
+ message: AI_LIMIT_REACHED_MESSAGE,
1020
+ // The module's own refusal keeps its code, so a workflow can branch on it.
1021
+ code: 'ai_limit_reached',
1022
+ })
1023
+ const embed = loadHandler(
1024
+ aiGenerateTextEmbedding.generateServerHandler!({}),
1025
+ 'ai-generate-text-embedding',
1026
+ { openai: openai.module, [SEGMENT_ROUTE_RUNTIME]: refusing }
1027
+ )
1028
+ expect(await embed({ text: 'hello', token: 'sk-test', optional: true }, {})).toMatchObject({
1029
+ embedding: [],
1030
+ skipped: true,
1031
+ skipReason: AI_LIMIT_REACHED_MESSAGE,
1032
+ })
1033
+ expect(openai.calls).toHaveLength(0)
1034
+ })
1035
+ })
1036
+
1037
+ // ---------------------------------------------------------------------------
1038
+ // general-rate-limiter on the shared limiter
1039
+ // ---------------------------------------------------------------------------
1040
+
1041
+ describe('visitor input is derived, not assumed', () => {
1042
+ const edge = (source: string, target: string) => ({ id: source + '>' + target, source, target })
1043
+ const node = (id: string, type: string, config: Record<string, unknown> = {}) =>
1044
+ ({ id, type, label: type, config } as any)
1045
+
1046
+ // A custom-js step exactly as the editor exports it (examples/uidl-samples/
1047
+ // project.json): `label` is its type's, the builder's label travels in `name`
1048
+ // (in `builderLabel` once the owner renamed the step).
1049
+ const sample = JSON.parse(
1050
+ fs.readFileSync(path.resolve(__dirname, '../../../examples/uidl-samples/project.json'), 'utf8')
1051
+ )
1052
+ const sampleCustomJs = (() => {
1053
+ const all = [
1054
+ ...Object.values(sample.workflows.workflows || {}),
1055
+ ...Object.values(sample.workflows.customNodes || {}),
1056
+ ] as any[]
1057
+ for (const graph of all) {
1058
+ const found = (graph.nodes || []).find((n: any) => n.type === 'general-custom-js')
1059
+ if (found) {
1060
+ return found
1061
+ }
1062
+ }
1063
+ throw new Error('the sample export has no custom-js step')
1064
+ })()
1065
+ const exported = (id: string, name: string, extra: Record<string, unknown> = {}) => ({
1066
+ ...sampleCustomJs,
1067
+ id,
1068
+ name,
1069
+ config: { ...sampleCustomJs.config, context: 'server' },
1070
+ executionEnv: 'server',
1071
+ ...extra,
1072
+ })
1073
+
1074
+ it('reads the export shape: the type label, the builder label in name', () => {
1075
+ expect(sampleCustomJs.label).toBe('Custom JavaScript')
1076
+ expect(typeof sampleCustomJs.name).toBe('string')
1077
+ })
1078
+
1079
+ it('trusts owner-settings reads and the owner-notification builders, by name or builderLabel', () => {
1080
+ const nodes = [
1081
+ node('form', 'general-extract-form-data'),
1082
+ node('settings', 'ecommerce-get-settings'),
1083
+ node('customer', 'data-select', { tableName: 'orders' }),
1084
+ node('bookingSettings', 'data-select', { tableName: 'teleport_booking_settings' }),
1085
+ exported('payload', 'Build Order-Notification Email Payload'),
1086
+ exported('renamed', 'Tell me about stock', { builderLabel: 'Build Low-Stock Email Payload' }),
1087
+ exported('browserPayload', 'Build Order-Notification Email Payload'),
1088
+ exported('prepare', 'Prepare Email'),
1089
+ ]
1090
+ const trustSets = collectMessageTrust({
1091
+ nodes,
1092
+ edges: [edge('t', 'form'), edge('form', 'prepare'), edge('prepare', 'payload')],
1093
+ // The browser runs `browserPayload`: a builder label does not make it the server's.
1094
+ serverNodeIds: new Set([
1095
+ 'settings',
1096
+ 'customer',
1097
+ 'bookingSettings',
1098
+ 'payload',
1099
+ 'renamed',
1100
+ 'prepare',
1101
+ ]),
1102
+ })
1103
+ expect(trustSets.ownerRecipientNodeIds.sort()).toEqual([
1104
+ 'bookingSettings',
1105
+ 'payload',
1106
+ 'renamed',
1107
+ 'settings',
1108
+ ])
1109
+ expect(trustSets.trustedHtmlNodeIds.sort()).toEqual(['payload', 'renamed'])
1110
+ })
1111
+
1112
+ it("trusts the booking alert recipients builder as an owner recipient, never as a body's HTML", () => {
1113
+ // The booking sender's notice to the business: a read of the settings row,
1114
+ // then the builder that turns its addresses into the list (with the
1115
+ // project owner's as the fallback) — after the visitor's booking was read.
1116
+ const trustSets = collectMessageTrust({
1117
+ nodes: [
1118
+ exported('tokens', 'Build Booking Email Tokens'),
1119
+ node('alertSettings', 'data-select', { tableName: 'teleport_booking_settings' }),
1120
+ exported('alertRecipients', 'Build Booking Alert Recipients'),
1121
+ exported('browserRecipients', 'Build Booking Alert Recipients'),
1122
+ ],
1123
+ edges: [edge('tokens', 'alertSettings'), edge('alertSettings', 'alertRecipients')],
1124
+ serverNodeIds: new Set(['tokens', 'alertSettings', 'alertRecipients']),
1125
+ })
1126
+ expect(trustSets.ownerRecipientNodeIds.sort()).toEqual(['alertRecipients', 'alertSettings'])
1127
+ expect(trustSets.trustedHtmlNodeIds).toEqual([])
1128
+ })
1129
+
1130
+ it('trusts a server pass-through only when it has inputs and each is an owner read', () => {
1131
+ const trustSets = collectMessageTrust({
1132
+ nodes: [
1133
+ node('settings', 'ecommerce-get-settings'),
1134
+ node('when', 'general-if-statement'),
1135
+ node('shape', 'general-custom-js'),
1136
+ node('root', 'general-custom-js'),
1137
+ ],
1138
+ edges: [edge('settings', 'when'), edge('when', 'shape')],
1139
+ serverNodeIds: new Set(['settings', 'when', 'shape', 'root']),
1140
+ })
1141
+ // `root` has no inputs: it reads the caller's (a custom node's parameters).
1142
+ expect(trustSets.ownerRecipientNodeIds.sort()).toEqual(['settings', 'shape'])
1143
+ })
1144
+
1145
+ it("counts a custom node's first step as the caller's: Prepare → email is capped and refused", () => {
1146
+ const prepare = exported('prepare', 'Prepare')
1147
+ const send = {
1148
+ id: 'send',
1149
+ type: 'email-resend',
1150
+ label: 'Send Email (Resend)',
1151
+ name: 'Email The Friend',
1152
+ config: { to: wfCtx('prepare', ['email']), body: wfCtx('prepare', ['body']) },
1153
+ } as any
1154
+ const serverSegment = {
1155
+ id: 'seg',
1156
+ env: 'server',
1157
+ nodeIds: ['prepare', 'send'],
1158
+ nodes: [prepare, send],
1159
+ edges: [edge('prepare', 'send')],
1160
+ } as any
1161
+ const graph = {
1162
+ id: 'cn',
1163
+ nodes: [prepare, send],
1164
+ edges: serverSegment.edges,
1165
+ segments: [serverSegment],
1166
+ } as any
1167
+ const segmentTrust = buildSegmentTrust(serverSegment, graph)
1168
+ expect(segmentTrust.messageTrust).toEqual({ ownerRecipientNodeIds: [], trustedHtmlNodeIds: [] })
1169
+ const warnings: string[] = []
1170
+ const saved = console.warn
1171
+ console.warn = (...args: unknown[]) => warnings.push(args.map(String).join(' '))
1172
+ let marked
1173
+ try {
1174
+ marked = markVisitorBoundMessageNodes(serverSegment, segmentTrust, undefined, 'Share')
1175
+ } finally {
1176
+ console.warn = saved
1177
+ }
1178
+ expect((marked.nodes[1].config as any).__visitorRecipientFields).toEqual(['to'])
1179
+ expect((marked.nodes[1].config as any).__refuseSend).toBeTruthy()
1180
+ // The build log names the step, not only its type.
1181
+ expect(warnings[0]).toContain('"Email The Friend"')
1182
+ })
1183
+
1184
+ it('counts a recipient laundered through a server step, from the graph the route is built with', () => {
1185
+ const nodes = [
1186
+ node('form', 'general-extract-form-data'),
1187
+ exported('prepare', 'Prepare Email'),
1188
+ node('send', 'email-resend', { to: wfCtx('prepare', ['email']), body: '<p>Thanks</p>' }),
1189
+ ]
1190
+ const serverSegment = {
1191
+ id: 'seg',
1192
+ env: 'server',
1193
+ nodeIds: ['prepare', 'send'],
1194
+ nodes: nodes.slice(1),
1195
+ edges: [edge('prepare', 'send')],
1196
+ } as any
1197
+ const graph = {
1198
+ id: 'wf',
1199
+ nodes,
1200
+ edges: [edge('form', 'prepare'), edge('prepare', 'send')],
1201
+ segments: [
1202
+ { id: 'client', env: 'client', nodeIds: ['form'], nodes: [nodes[0]], edges: [] },
1203
+ serverSegment,
1204
+ ],
1205
+ } as any
1206
+ const segmentTrust = buildSegmentTrust(serverSegment, graph)
1207
+ expect(segmentTrust.messageTrust!.ownerRecipientNodeIds).toEqual([])
1208
+ const marked = markVisitorBoundMessageNodes(serverSegment, segmentTrust, undefined)
1209
+ expect((marked.nodes[1].config as any).__visitorRecipientFields).toEqual(['to'])
1210
+ })
1211
+
1212
+ it.each([
1213
+ [
1214
+ 'the order notification',
1215
+ 'Build Order-Notification Email Payload',
1216
+ 'Send Order-Notification Email',
1217
+ ],
1218
+ ['the low-stock alert', 'Build Low-Stock Email Payload', 'Send Low-Stock Alert Email'],
1219
+ ])(
1220
+ 'sends %s of a checkout, built in the export shape, to the owner as HTML',
1221
+ async (_what, builder, sendName) => {
1222
+ // The checkout's tail as the editor exports it: the browser's form, then
1223
+ // the server's payload builder (owner addresses baked, HTML from rows) and
1224
+ // the send step bound to its output.
1225
+ const payload = exported('payload', builder, {
1226
+ config: {
1227
+ context: 'server',
1228
+ code:
1229
+ 'function customHandler(params) { return { recipients: ["owner@shop.example"], ' +
1230
+ 'subject: "New order #1001", html: "<p>Order <b>#1001</b> &amp; more</p>" } }',
1231
+ },
1232
+ })
1233
+ const send = {
1234
+ id: 'send',
1235
+ type: 'email-resend',
1236
+ label: 'Send Email (Resend)',
1237
+ name: sendName,
1238
+ executionEnv: 'server',
1239
+ stepNumber: 3,
1240
+ config: {
1241
+ to: wfCtx('payload', ['recipients']),
1242
+ subject: wfCtx('payload', ['subject']),
1243
+ body: wfCtx('payload', ['html']),
1244
+ from: 'shop@shop.example',
1245
+ apiKey: 're_test',
1246
+ emailType: 'transactional',
1247
+ },
1248
+ } as any
1249
+ const form = {
1250
+ ...node('form', 'general-extract-form-data'),
1251
+ executionEnv: 'client',
1252
+ stepNumber: 1,
1253
+ }
1254
+ const serverSegment = {
1255
+ id: 'seg-tail',
1256
+ env: 'server',
1257
+ nodeIds: ['payload', 'send'],
1258
+ nodes: [{ ...payload, stepNumber: 2 }, send],
1259
+ edges: [edge('payload', 'send')],
1260
+ entryNodeIds: ['payload'],
1261
+ } as any
1262
+ const graph = {
1263
+ id: 'wf-checkout',
1264
+ nodes: [form, payload, send],
1265
+ edges: [edge('form', 'payload'), edge('payload', 'send')],
1266
+ segments: [
1267
+ { id: 'seg-form', env: 'client', nodeIds: ['form'], nodes: [form], edges: [] },
1268
+ serverSegment,
1269
+ ],
1270
+ } as any
1271
+ const route = generateServerSegmentAPIRoute(serverSegment, 'Place Order', undefined, {
1272
+ trust: buildSegmentTrust(serverSegment, graph),
1273
+ })
1274
+ expect(route).not.toContain('"__refuseSend":')
1275
+ expect(route).not.toContain('"__visitorRecipientFields":')
1276
+ expect(route).not.toContain('"__escapeBody":')
1277
+
1278
+ const sent: any[] = []
1279
+ class Resend {
1280
+ emails = {
1281
+ send: async (message: any) => {
1282
+ sent.push(message)
1283
+ return { data: { id: 'msg_1' } }
1284
+ },
1285
+ }
1286
+ }
1287
+ const savedSecret = process.env.NEXTAUTH_SECRET
1288
+ process.env.NEXTAUTH_SECRET = 'checkout-tail-test-secret'
1289
+ try {
1290
+ const runtime = loadServerRuntime()
1291
+ const security = loadSecurityRuntime()
1292
+ const requireStub = (id: string): unknown => {
1293
+ if (id.endsWith('server-runtime')) {
1294
+ return runtime
1295
+ }
1296
+ if (id.endsWith('security-runtime')) {
1297
+ return security
1298
+ }
1299
+ if (id === 'resend') {
1300
+ return { Resend }
1301
+ }
1302
+ return {}
1303
+ }
1304
+ const module: { exports: any } = { exports: {} }
1305
+ // eslint-disable-next-line no-new-func
1306
+ new Function('module', 'exports', 'require', 'console', route)(
1307
+ module,
1308
+ module.exports,
1309
+ requireStub,
1310
+ {
1311
+ warn: noop,
1312
+ error: noop,
1313
+ log: noop,
1314
+ }
1315
+ )
1316
+ const reply = { status: 0, body: null as any }
1317
+ await module.exports(
1318
+ {
1319
+ method: 'POST',
1320
+ body: { context: { form: { name: 'Ann' } } },
1321
+ headers: { host: 'localhost:3000' },
1322
+ socket: { remoteAddress: '203.0.113.7' },
1323
+ },
1324
+ {
1325
+ setHeader: noop,
1326
+ status(code: number) {
1327
+ reply.status = code
1328
+ return this
1329
+ },
1330
+ json(answer: unknown) {
1331
+ reply.body = answer
1332
+ return this
1333
+ },
1334
+ end: noop,
1335
+ }
1336
+ )
1337
+ expect(reply.status).toBe(200)
1338
+ expect(sent).toHaveLength(1)
1339
+ expect(sent[0].to).toEqual(['owner@shop.example'])
1340
+ expect(sent[0].html).toBe('<p>Order <b>#1001</b> &amp; more</p>')
1341
+ } finally {
1342
+ if (savedSecret === undefined) {
1343
+ delete process.env.NEXTAUTH_SECRET
1344
+ } else {
1345
+ process.env.NEXTAUTH_SECRET = savedSecret
1346
+ }
1347
+ }
1348
+ }
1349
+ )
1350
+ })
1351
+
1352
+ describe("a send whose whole message is the visitor's, to the visitor's address, never goes out", () => {
1353
+ const emailNode = (config: Record<string, unknown>) => ({
1354
+ id: 'send',
1355
+ type: 'email-resend',
1356
+ label: 'Send Message',
1357
+ config,
1358
+ })
1359
+ const one = (n: any): WorkflowSegment =>
1360
+ ({ id: 'seg', env: 'server', nodeIds: [n.id], nodes: [n], edges: [] } as any)
1361
+
1362
+ const markQuietly = (n: any) => {
1363
+ const warnings: string[] = []
1364
+ const saved = console.warn
1365
+ console.warn = (...args: unknown[]) => warnings.push(args.map(String).join(' '))
1366
+ try {
1367
+ return {
1368
+ marked: markVisitorBoundMessageNodes(one(n), undefined, undefined, 'Contact'),
1369
+ warnings,
1370
+ }
1371
+ } finally {
1372
+ console.warn = saved
1373
+ }
1374
+ }
1375
+
1376
+ it('refuses it when the site is generated, says why in the build log, and the guard never sends', async () => {
1377
+ const { marked, warnings } = markQuietly(
1378
+ emailNode({ to: wfCtx('form', ['email']), body: wfCtx('form', ['message']) })
1379
+ )
1380
+ const config = marked.nodes[0].config as any
1381
+ expect(config.__refuseSend).toMatch(/only the visitor's input/)
1382
+ expect(warnings).toHaveLength(1)
1383
+ expect(warnings[0]).toContain('Workflow "Contact"')
1384
+ expect(warnings[0]).toContain('"Send Message"')
1385
+
1386
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
1387
+ const result = await handler(
1388
+ { apiKey: 're', from: 'shop@example.com', to: 'x@example.com', body: 'spam', ...config },
1389
+ visitor('198.51.100.40')
1390
+ )
1391
+ expect(result).toEqual({ id: '', success: false, error: MESSAGE_NOT_SENT_MESSAGE })
1392
+ expect(sent).toHaveLength(0)
1393
+ })
1394
+
1395
+ it("allows the author's template with the visitor's values in it", () => {
1396
+ const { marked, warnings } = markQuietly(
1397
+ emailNode({
1398
+ to: wfCtx('form', ['email']),
1399
+ body: '<p>Thanks {{name}}, we got your message.</p>',
1400
+ templateParams: [{ key: 'name', value: wfCtx('form', ['name']) }],
1401
+ })
1402
+ )
1403
+ const config = marked.nodes[0].config as any
1404
+ expect(config.__refuseSend).toBeUndefined()
1405
+ expect(config.__visitorRecipientFields).toEqual(['to'])
1406
+ expect(warnings).toEqual([])
1407
+ })
1408
+
1409
+ it('refuses a whole-token body that is a visitor templateParam, and a visitor sender', () => {
1410
+ const tokenBody = markQuietly(
1411
+ emailNode({
1412
+ to: wfCtx('form', ['email']),
1413
+ body: '{{message}}',
1414
+ templateParams: [{ key: 'message', value: wfCtx('form', ['message']) }],
1415
+ })
1416
+ )
1417
+ expect((tokenBody.marked.nodes[0].config as any).__refuseSend).toBeTruthy()
1418
+ const sender = markQuietly(
1419
+ emailNode({ to: 'owner@shop.example', from: wfCtx('form', ['email']), body: '<p>x</p>' })
1420
+ )
1421
+ expect((sender.marked.nodes[0].config as any).__refuseSend).toMatch(/takes its sender \(from\)/)
1422
+ })
1423
+
1424
+ it("does the same for an SMS whose whole message is the visitor's", () => {
1425
+ const sms = (message: unknown) =>
1426
+ markQuietly({
1427
+ id: 'text',
1428
+ type: 'sms-twilio',
1429
+ label: 'Text',
1430
+ config: { to: wfCtx('form', ['phone']), message },
1431
+ })
1432
+ expect(
1433
+ (sms(wfCtx('form', ['message'])).marked.nodes[0].config as any).__refuseSend
1434
+ ).toBeTruthy()
1435
+ expect(
1436
+ (sms([wfCtx('form', ['message'])]).marked.nodes[0].config as any).__refuseSend
1437
+ ).toBeTruthy()
1438
+ const template = sms(['Your booking is confirmed, ', wfCtx('form', ['name'])])
1439
+ expect((template.marked.nodes[0].config as any).__refuseSend).toBeUndefined()
1440
+ })
1441
+
1442
+ it("counts markup around one visitor value as the visitor's whole message", () => {
1443
+ const fromVisitor = { to: wfCtx('form', ['email']) }
1444
+ const chip =
1445
+ '<span class="context-value-inline" data-ctx-node-id="form" ' +
1446
+ 'data-ctx-path=\'["form","message"]\'>Message</span>'
1447
+ const wrapped = [
1448
+ {
1449
+ ...fromVisitor,
1450
+ body: '<p>{{message}}</p>',
1451
+ templateParams: [{ key: 'message', value: wfCtx('form', ['message']) }],
1452
+ },
1453
+ { ...fromVisitor, body: '<div><p>' + chip + '</p><p>&nbsp;</p></div>' },
1454
+ ]
1455
+ for (const config of wrapped) {
1456
+ expect(
1457
+ (markQuietly(emailNode(config)).marked.nodes[0].config as any).__refuseSend
1458
+ ).toBeTruthy()
1459
+ }
1460
+ const authored = markQuietly(
1461
+ emailNode({ ...fromVisitor, body: '<p>You wrote: ' + chip + '</p>' })
1462
+ )
1463
+ expect((authored.marked.nodes[0].config as any).__refuseSend).toBeUndefined()
1464
+ })
1465
+
1466
+ it('refuses a sender name or SMS sender the visitor supplies', () => {
1467
+ const name = markQuietly(
1468
+ emailNode({ to: 'owner@shop.example', fromName: wfCtx('form', ['name']), body: '<p>x</p>' })
1469
+ )
1470
+ expect((name.marked.nodes[0].config as any).__refuseSend).toMatch(
1471
+ /takes its sender \(fromName\)/
1472
+ )
1473
+ const smsSender = markQuietly({
1474
+ id: 'text',
1475
+ type: 'sms-twilio',
1476
+ label: 'Text',
1477
+ config: { to: '+15550000000', from: wfCtx('form', ['sender']), message: 'Hi' },
1478
+ })
1479
+ expect((smsSender.marked.nodes[0].config as any).__refuseSend).toMatch(
1480
+ /takes its sender \(from\)/
1481
+ )
1482
+ })
1483
+
1484
+ it('escapes a whole-value body read from a row, but sends a platform HTML builder as it is', () => {
1485
+ const fromRow = markVisitorBoundMessageNodes(
1486
+ one(emailNode({ to: 'owner@shop.example', body: wfCtx('lookup', ['rows', 0, 'note']) })),
1487
+ undefined,
1488
+ undefined
1489
+ )
1490
+ expect((fromRow.nodes[0].config as any).__escapeBody).toBe(true)
1491
+ const fromBuilder = markVisitorBoundMessageNodes(
1492
+ one(emailNode({ to: 'owner@shop.example', body: wfCtx('payload', ['html']) })),
1493
+ {
1494
+ ...({} as SegmentTrust),
1495
+ messageTrust: { ownerRecipientNodeIds: ['payload'], trustedHtmlNodeIds: ['payload'] },
1496
+ } as SegmentTrust,
1497
+ undefined
1498
+ )
1499
+ expect((fromBuilder.nodes[0].config as any).__escapeBody).toBeUndefined()
1500
+ })
1501
+ })
1502
+
1503
+ describe('a capped send is skipped, never aborts; webhooks count per recipient', () => {
1504
+ const capped = (extra: Record<string, unknown> = {}) => ({
1505
+ apiKey: 're',
1506
+ from: 'shop@example.com',
1507
+ subject: 's',
1508
+ body: '<p>b</p>',
1509
+ __visitorRecipientFields: ['to'],
1510
+ ...extra,
1511
+ })
1512
+
1513
+ it('answers a skipped result the runtime does not treat as a failure', async () => {
1514
+ const { handler } = loadEmailNode(loadSecurityRuntime())
1515
+ let last: any
1516
+ for (let i = 0; i < 4; i++) {
1517
+ last = await handler(capped({ to: 'same@example.com' }), visitor('198.51.100.' + (90 + i)))
1518
+ }
1519
+ expect(last).toEqual({ id: '', skipped: true, reason: TOO_MANY_MESSAGES_MESSAGE })
1520
+ expect(loadServerRuntime().isFatalNodeResult(last)).toBeFalsy()
1521
+ })
1522
+
1523
+ it('does not count a public webhook caller by its address', async () => {
1524
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
1525
+ for (let i = 1; i <= 7; i++) {
1526
+ await handler(
1527
+ capped({ to: 'respondent' + i + '@example.com', __noIpCap: true }),
1528
+ visitor('203.0.113.50')
1529
+ )
1530
+ }
1531
+ // An integration on one address mails each respondent: 7 sends, no per-IP stop.
1532
+ expect(sent).toHaveLength(7)
1533
+ })
1534
+
1535
+ it("counts a custom node's sends made for a public webhook, and exempts every other internal call", async () => {
1536
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
1537
+ const internal = (publicCaller: boolean) => ({
1538
+ __request: {
1539
+ headers: publicCaller ? { 'x-teleport-public-caller': '1' } : {},
1540
+ remoteAddress: '10.0.0.2',
1541
+ internal: true,
1542
+ },
1543
+ })
1544
+ for (let i = 0; i < 4; i++) {
1545
+ await handler(capped({ to: 'cron@example.com' }), internal(false))
1546
+ }
1547
+ expect(sent).toHaveLength(4)
1548
+ const results = []
1549
+ for (let i = 0; i < 4; i++) {
1550
+ results.push(await handler(capped({ to: 'hooked@example.com' }), internal(true)))
1551
+ }
1552
+ expect(results.map((r) => r.success)).toEqual([true, true, true, undefined])
1553
+ })
1554
+
1555
+ it('marks an unsigned webhook that calls a custom node as a public caller', () => {
1556
+ const route = generateWebhookWorkflowAPIRoute({
1557
+ id: 'wf-hook-cn',
1558
+ name: 'Inbound',
1559
+ trigger: { type: 'event-webhook-triggered', nodeId: 't', scope: 'global', config: {} },
1560
+ nodes: [
1561
+ {
1562
+ id: 'call',
1563
+ type: 'general-custom-node',
1564
+ config: { customNodeId: 'cn-mail' },
1565
+ stepNumber: 1,
1566
+ label: 'Custom Node',
1567
+ },
1568
+ ],
1569
+ edges: [{ id: 'e1', source: 't', target: 'call' }],
1570
+ webhookConfig: { httpMethod: 'POST', urlPath: 'inbound', verifySignature: false },
1571
+ } as any)
1572
+ expect(route).toContain("context.__internalHeaders['x-teleport-public-caller'] = '1';")
1573
+ })
1574
+ })
1575
+
1576
+ describe('the recipient cap counts one mailbox or number however it is written', () => {
1577
+ it('folds case and +tags for email, and 00 / + / punctuation for phones', async () => {
1578
+ const { handler, sent } = loadEmailNode(loadSecurityRuntime())
1579
+ // Gmail also ignores dots in the mailbox name.
1580
+ const variants = [
1581
+ 'Vic.Tim@Gmail.com',
1582
+ 'victim+1@gmail.com',
1583
+ 'v.i.c.t.i.m+news@googlemail.com',
1584
+ 'victim+x@gmail.com',
1585
+ ]
1586
+ const results = []
1587
+ for (let i = 0; i < variants.length; i++) {
1588
+ results.push(
1589
+ await handler(
1590
+ {
1591
+ apiKey: 're',
1592
+ from: 'shop@example.com',
1593
+ to: variants[i],
1594
+ subject: 's',
1595
+ body: '<p>b</p>',
1596
+ __visitorRecipientFields: ['to'],
1597
+ },
1598
+ visitor('198.51.100.' + (60 + i))
1599
+ )
1600
+ )
1601
+ }
1602
+ expect(results.map((r) => r.success)).toEqual([true, true, true, undefined])
1603
+ expect(sent).toHaveLength(3)
1604
+
1605
+ const texts: any[] = []
1606
+ const twilio = () => ({
1607
+ messages: {
1608
+ create: async (payload: any) => {
1609
+ texts.push(payload)
1610
+ return { sid: 'SM', status: 'queued' }
1611
+ },
1612
+ },
1613
+ })
1614
+ const sms = loadHandler(smsTwilio.generateServerHandler!({}), 'sms-twilio', {
1615
+ twilio,
1616
+ [SEGMENT_ROUTE_RUNTIME]: loadSecurityRuntime(),
1617
+ })
1618
+ const numbers = ['+1 (555) 123-4567', '15551234567', '0015551234567', '+1-555-123-4567']
1619
+ const smsResults = []
1620
+ for (let i = 0; i < numbers.length; i++) {
1621
+ smsResults.push(
1622
+ await sms(
1623
+ {
1624
+ accountSid: 'AC',
1625
+ authToken: 't',
1626
+ from: '+15550000000',
1627
+ to: numbers[i],
1628
+ message: 'Hi',
1629
+ __visitorRecipientFields: ['to'],
1630
+ },
1631
+ visitor('198.51.100.' + (70 + i))
1632
+ )
1633
+ )
1634
+ }
1635
+ expect(smsResults.map((r) => r.success)).toEqual([true, true, true, undefined])
1636
+ })
1637
+ })
1638
+
1639
+ describe('a public webhook is marked like a page route; a signed one is not', () => {
1640
+ const webhook = (webhookConfig: Record<string, unknown>) =>
1641
+ ({
1642
+ id: 'wf-hook',
1643
+ name: 'Inbound',
1644
+ trigger: { type: 'event-webhook-triggered', nodeId: 't', scope: 'global', config: {} },
1645
+ nodes: [
1646
+ {
1647
+ id: 'reply',
1648
+ type: 'email-resend',
1649
+ config: { to: wfCtx('t', ['body', 'email']), subject: 'Thanks', body: '<p>Thanks</p>' },
1650
+ stepNumber: 1,
1651
+ label: 'Reply',
1652
+ },
1653
+ ],
1654
+ edges: [{ id: 'e1', source: 't', target: 'reply' }],
1655
+ webhookConfig: { httpMethod: 'POST', urlPath: 'inbound', ...webhookConfig },
1656
+ } as any)
1657
+
1658
+ it("counts the payload's recipient and hands the nodes the request when nothing is verified", () => {
1659
+ const route = generateWebhookWorkflowAPIRoute(webhook({ verifySignature: false }))
1660
+ expect(route).toMatch(/"__visitorRecipientFields": \[\s*"to"\s*\]/)
1661
+ expect(route).toContain('context.__request = { headers: req.headers || {}, remoteAddress:')
1662
+ })
1663
+
1664
+ it('trusts a payload a verified signature vouches for', () => {
1665
+ const route = generateWebhookWorkflowAPIRoute(
1666
+ webhook({
1667
+ verifySignature: true,
1668
+ signatureAlgorithm: 'hmac-sha256',
1669
+ signatureSecret: 'HOOK_SECRET',
1670
+ })
1671
+ )
1672
+ expect(route).not.toContain('"__visitorRecipientFields":')
1673
+ expect(route).not.toContain('context.__request = { headers: req.headers || {}, remoteAddress:')
1674
+ })
1675
+
1676
+ it('treats a custom scheme (left to the workflow) as unverified', () => {
1677
+ const route = generateWebhookWorkflowAPIRoute(
1678
+ webhook({ verifySignature: true, signatureAlgorithm: 'custom' })
1679
+ )
1680
+ expect(route).toContain('"__visitorRecipientFields":')
1681
+ })
1682
+ })
1683
+
1684
+ describe('the AI output budget of one call has a ceiling', () => {
1685
+ it('lowers a maxTokens above 4096 to it and logs why', async () => {
1686
+ const warnings: string[] = []
1687
+ const openai = fakeOpenAI()
1688
+ const source = generateAIStreamingProviderUtils('../../..')
1689
+ const requireShim = (id: string): unknown =>
1690
+ id === 'openai'
1691
+ ? openai.module
1692
+ : id === SEGMENT_ROUTE_RUNTIME
1693
+ ? { rateLimit: async () => ({ allowed: true, retryAfter: 0 }) }
1694
+ : undefined
1695
+ // eslint-disable-next-line no-new-func
1696
+ const call = new Function('require', 'console', `${source}\nreturn __ai_callProvider;`)(
1697
+ requireShim,
1698
+ {
1699
+ warn: (...args: unknown[]) => warnings.push(args.map(String).join(' ')),
1700
+ error: noop,
1701
+ log: noop,
1702
+ }
1703
+ )
1704
+ await call({
1705
+ provider: 'openai',
1706
+ model: 'gpt-4o',
1707
+ token: 'sk',
1708
+ userMessage: 'Hi',
1709
+ maxTokens: 100000,
1710
+ })
1711
+ expect(openai.calls[0].max_tokens).toBe(4096)
1712
+ expect(warnings.join('\n')).toContain('maxTokens 100000 is above the per-call ceiling of 4096')
1713
+ await call({
1714
+ provider: 'openai',
1715
+ model: 'gpt-4o',
1716
+ token: 'sk',
1717
+ userMessage: 'Hi',
1718
+ maxTokens: 300,
1719
+ })
1720
+ expect(openai.calls[1].max_tokens).toBe(300)
1721
+ })
1722
+ })
1723
+
1724
+ describe('similarity-scoring embeddings count against the AI budget', () => {
1725
+ it('refuses the semantic score when the budget is spent, without calling the provider', async () => {
1726
+ const openai = fakeOpenAI()
1727
+ const score = loadHandler(
1728
+ utilitySimilarityScoring.generateServerHandler!({}),
1729
+ 'utility-similarity-scoring',
1730
+ {
1731
+ openai: openai.module,
1732
+ [SEGMENT_ROUTE_RUNTIME]: { rateLimit: async () => ({ allowed: false, retryAfter: 60 }) },
1733
+ }
1734
+ )
1735
+ const result = await score(
1736
+ { text1: 'car', text2: 'auto', algorithm: 'semantic', token: 'sk-test' },
1737
+ {}
1738
+ )
1739
+ expect(result).toMatchObject({ score: 0, error: AI_LIMIT_REACHED_MESSAGE })
1740
+ expect(openai.calls).toHaveLength(0)
1741
+ })
1742
+ })
1743
+
1744
+ describe('a segment route shows the AI refusals that name nothing secret', () => {
1745
+ const savedSecret = process.env.NEXTAUTH_SECRET
1746
+ beforeAll(() => {
1747
+ process.env.NEXTAUTH_SECRET = 'outbound-nodes-test-secret'
1748
+ })
1749
+ afterAll(() => {
1750
+ if (savedSecret === undefined) {
1751
+ delete process.env.NEXTAUTH_SECRET
1752
+ } else {
1753
+ process.env.NEXTAUTH_SECRET = savedSecret
1754
+ }
1755
+ })
1756
+
1757
+ const askSegment = {
1758
+ id: 'seg-ask',
1759
+ env: 'server',
1760
+ nodeIds: ['ask'],
1761
+ nodes: [
1762
+ {
1763
+ id: 'ask',
1764
+ type: 'ai-custom-prompt',
1765
+ config: { prompt: wfCtx('form', ['question']), token: 'sk-test', model: 'gpt-4o' },
1766
+ stepNumber: 1,
1767
+ },
1768
+ ],
1769
+ edges: [],
1770
+ } as unknown as WorkflowSegment
1771
+
1772
+ const ask = async (question: string, aiAllowed: boolean, providerFails = false) => {
1773
+ const real = loadSecurityRuntime()
1774
+ const security = {
1775
+ ...real,
1776
+ rateLimit: async (o: { key: string }) =>
1777
+ o.key === 'site:ai-calls' ? { allowed: aiAllowed, retryAfter: 3600 } : real.rateLimit(o),
1778
+ }
1779
+ class OpenAI {
1780
+ chat = {
1781
+ completions: {
1782
+ create: async () => {
1783
+ if (providerFails) {
1784
+ throw new Error('401 Incorrect API key provided: sk-te**')
1785
+ }
1786
+ return { choices: [{ message: { content: 'ok' } }], usage: {} }
1787
+ },
1788
+ },
1789
+ }
1790
+ }
1791
+ const runtime = loadServerRuntime()
1792
+ const requireStub = (id: string): unknown => {
1793
+ if (id.endsWith('server-runtime')) {
1794
+ return runtime
1795
+ }
1796
+ if (id.endsWith('security-runtime')) {
1797
+ return security
1798
+ }
1799
+ if (id === 'openai') {
1800
+ return { default: OpenAI }
1801
+ }
1802
+ return {}
1803
+ }
1804
+ const route: { exports: any } = { exports: {} }
1805
+ // eslint-disable-next-line no-new-func
1806
+ new Function(
1807
+ 'module',
1808
+ 'exports',
1809
+ 'require',
1810
+ 'console',
1811
+ generateServerSegmentAPIRoute(askSegment, 'Ask')
1812
+ )(route, route.exports, requireStub, { warn: noop, error: noop, log: noop })
1813
+ const reply = { status: 0, body: null as any }
1814
+ const res = {
1815
+ setHeader: noop,
1816
+ status(code: number) {
1817
+ reply.status = code
1818
+ return this
1819
+ },
1820
+ json(payload: unknown) {
1821
+ reply.body = payload
1822
+ return this
1823
+ },
1824
+ end: noop,
1825
+ }
1826
+ await route.exports(
1827
+ {
1828
+ method: 'POST',
1829
+ body: { context: { form: { question } } },
1830
+ headers: { host: 'localhost:3000' },
1831
+ socket: { remoteAddress: '203.0.113.7' },
1832
+ },
1833
+ res
1834
+ )
1835
+ return reply
1836
+ }
1837
+
1838
+ it('answers a spent daily budget with 429 and its own message', async () => {
1839
+ const reply = await ask('Hi', false)
1840
+ expect(reply.status).toBe(429)
1841
+ expect(reply.body).toMatchObject({ error: AI_LIMIT_REACHED_MESSAGE })
1842
+ expect(typeof reply.body.id).toBe('string')
1843
+ })
1844
+
1845
+ it('answers a prompt over the cap with 400 and its own message', async () => {
1846
+ const reply = await ask('x'.repeat(8001), true)
1847
+ expect(reply.status).toBe(400)
1848
+ expect(reply.body).toMatchObject({ error: AI_PROMPT_TOO_LONG_MESSAGE })
1849
+ })
1850
+
1851
+ it('keeps every other failure generic', async () => {
1852
+ const reply = await ask('Hi', true, true)
1853
+ expect(reply.status).toBe(500)
1854
+ expect(reply.body).toMatchObject({ error: SEGMENT_PUBLIC_ERROR })
1855
+ expect(JSON.stringify(reply.body)).not.toMatch(/Incorrect|sk-te|refused/)
1856
+ })
1857
+ })
1858
+
1859
+ describe('general-rate-limiter counts in the shared limiter', () => {
1860
+ const loadLimiter = (security: unknown) =>
1861
+ loadHandler(generalRateLimiter.generateServerHandler!({}), 'general-rate-limiter', {
1862
+ [SEGMENT_ROUTE_RUNTIME]: security,
1863
+ })
1864
+
1865
+ it('two instances of the route count together, not each in its own memory', async () => {
1866
+ const security = loadSecurityRuntime()
1867
+ const first = loadLimiter(security)
1868
+ const second = loadLimiter(security)
1869
+ const config = { strategy: 'ip', maxRequests: 2, windowMs: 60000, __nodeId: 'limit' }
1870
+ expect(await first(config, visitor('198.51.100.1'))).toEqual({ allowed: true })
1871
+ expect(await second(config, visitor('198.51.100.1'))).toEqual({ allowed: true })
1872
+ const refused = await first(config, visitor('198.51.100.1'))
1873
+ expect(refused.__earlyResponse.status).toBe(429)
1874
+ expect(refused.__earlyResponse.headers['Retry-After']).toMatch(/^\d+$/)
1875
+ // Another visitor has its own count.
1876
+ expect(await second(config, visitor('198.51.100.2'))).toEqual({ allowed: true })
1877
+ })
1878
+
1879
+ it('ignores a forwarded address a visitor wrote and refuses without the runtime', async () => {
1880
+ const security = loadSecurityRuntime()
1881
+ const limiter = loadLimiter(security)
1882
+ const config = { strategy: 'ip', maxRequests: 1, windowMs: 60000, __nodeId: 'limit' }
1883
+ const spoofed = (hop: string) => ({
1884
+ __request: { headers: { 'x-forwarded-for': hop }, remoteAddress: '198.51.100.77' },
1885
+ })
1886
+ expect(await limiter(config, spoofed('1.1.1.1'))).toEqual({ allowed: true })
1887
+ expect((await limiter(config, spoofed('2.2.2.2'))).__earlyResponse.status).toBe(429)
1888
+
1889
+ const without = loadLimiter(null)
1890
+ expect((await without(config, visitor('198.51.100.1'))).__earlyResponse.status).toBe(500)
1891
+ })
1892
+
1893
+ it('keys a visitor exactly as the shared runtime does, an IPv4 address inside IPv6 included', async () => {
1894
+ const security = loadSecurityRuntime()
1895
+ const keyed: string[] = []
1896
+ const limiter = loadLimiter({
1897
+ ...security,
1898
+ rateLimit: async (options: { key: string }) => {
1899
+ keyed.push(JSON.parse(options.key)[3])
1900
+ return { allowed: true, retryAfter: 0 }
1901
+ },
1902
+ })
1903
+ const config = { strategy: 'ip', maxRequests: 100, windowMs: 60000, __nodeId: 'limit' }
1904
+ const addresses = [
1905
+ '198.51.100.7',
1906
+ // IPv4-mapped: the runtime reads it as the IPv4 visitor it is.
1907
+ '::ffff:198.51.100.7',
1908
+ '2001:db8:1:2::9',
1909
+ // An IPv4 tail fills the last two groups: the /64 is 2001:db8:0:5.
1910
+ '2001:db8::5:6:7:198.51.100.7',
1911
+ ]
1912
+ for (const address of addresses) {
1913
+ await limiter(config, visitor(address))
1914
+ }
1915
+ expect(keyed).toEqual([
1916
+ '198.51.100.7',
1917
+ '198.51.100.7',
1918
+ '2001:db8:1:2::/64',
1919
+ '2001:db8:0:5::/64',
1920
+ ])
1921
+ expect(keyed).toEqual(
1922
+ addresses.map((address) =>
1923
+ security.visitorKey(security.clientIp({ headers: {}, socket: { remoteAddress: address } }))
1924
+ )
1925
+ )
1926
+ await limiter(config, { __request: undefined })
1927
+ expect(keyed[keyed.length - 1]).toBe('unknown')
1928
+ })
1929
+ })