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

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,2275 @@
1
+ /* tslint:disable:max-classes-per-file */
2
+ import * as nodeCrypto from 'crypto'
3
+ import * as net from 'net'
4
+ import type { UIDLAuthentication } from '@teleporthq/teleport-types'
5
+ import {
6
+ generateAuthDbHealthFile,
7
+ generateAuthOptionsFile,
8
+ generateHashPasswordFile,
9
+ generateMiddlewareFile,
10
+ generateNextAuthRouteFile,
11
+ generateSignupRouteFile,
12
+ isPrivilegedUserField,
13
+ resolveSignupFields,
14
+ SIGN_IN_FAILURE_LIMIT,
15
+ SIGN_IN_VISITOR_FAILURE_LIMIT,
16
+ } from '../src/auth-generator'
17
+ import {
18
+ FORGOT_PASSWORD_ANSWER,
19
+ generateForgotPasswordRoute,
20
+ generateResetPasswordRoute,
21
+ resolveResetPagePath,
22
+ } from '../src/password-reset-route-generator'
23
+ import { generateSessionRevocationFile } from '../src/session-revocation-generator'
24
+ import { generateSecurityRuntimeCode } from '../src/security-runtime/security-runtime-code'
25
+ import { generatePageAccessGuardModule } from '../src/page-access-guard-generator'
26
+ import { generateWorkflowAuthHelperFile } from '../src/workflow-auth-generator'
27
+ import { generateAccountDeleteRoute } from '../src/account-delete-route-generator'
28
+ import { generateCommonJsSessionTokenResolverCode } from '../src/session-cookie-resolver'
29
+ import { generateDataAPIRoute } from '../src/data-api-route-generator'
30
+ import { NextWorkflowProjectPlugin } from '../src/workflow-project-plugin'
31
+ import { accountRequestPasswordReset } from '../src/nodes/account/account-request-password-reset'
32
+ import { accountResetPassword } from '../src/nodes/account/account-reset-password'
33
+ import { accountHashPassword } from '../src/nodes/account/account-hash-password'
34
+ import { accountComparePasswords } from '../src/nodes/account/account-compare-passwords'
35
+
36
+ /**
37
+ * Task 3a of the 2026-10-25 security round, EXECUTED: the generated auth
38
+ * modules and routes run against a fake Postgres that answers only the
39
+ * statements below, with the real security runtime (in-memory limits).
40
+ *
41
+ * A3-C1 (pre-auth account takeover): the forgot-password workflow minted the
42
+ * reset token in the browser and stored it with the email the browser named,
43
+ * so `{ email: victim, token: mine }` followed by `{ token: mine, password }`
44
+ * set anyone's password. Now the routes mint, hash, bind and spend the token
45
+ * on the server, and nothing a request sends can name one.
46
+ */
47
+
48
+ type Row = Record<string, any>
49
+
50
+ const sha256 = (value: string) => nodeCrypto.createHash('sha256').update(value).digest('hex')
51
+
52
+ // bcryptjs is not installed in the generator repo: a stand-in with its two calls.
53
+ const fakeBcrypt = {
54
+ hashSync: (value: string) => 'bcrypt:' + sha256(value),
55
+ compareSync: (value: string, hash: string) => hash === 'bcrypt:' + sha256(value),
56
+ }
57
+
58
+ const pepperedHash = (password: string, pepper: string) =>
59
+ fakeBcrypt.hashSync(
60
+ nodeCrypto
61
+ .createHash('sha256')
62
+ .update(password + pepper)
63
+ .digest('base64')
64
+ )
65
+
66
+ interface Reset {
67
+ token_hash: string
68
+ user_id: string
69
+ expires_at: number
70
+ used_at: number | null
71
+ }
72
+
73
+ class FakePostgres {
74
+ users: Row[] = []
75
+ resets: Reset[] = []
76
+ resetsTableExists = false
77
+ now = Date.UTC(2026, 9, 25, 12)
78
+ statements: string[] = []
79
+ // The account store cannot be read (a connection refused) for lookups by id.
80
+ failLookups = false
81
+
82
+ query(sql: string, values: any[] = []): { rows: Row[]; rowCount?: number } {
83
+ this.statements.push(sql)
84
+ if (/^(BEGIN|COMMIT|ROLLBACK)$/.test(sql) || sql.startsWith('SELECT pg_advisory_xact_lock')) {
85
+ return { rows: [] }
86
+ }
87
+ if (sql.startsWith('CREATE TABLE IF NOT EXISTS teleport_password_resets')) {
88
+ this.resetsTableExists = true
89
+ return { rows: [] }
90
+ }
91
+ if (sql.startsWith('CREATE INDEX IF NOT EXISTS teleport_password_resets_user_id_idx')) {
92
+ return { rows: [] }
93
+ }
94
+ if (sql === 'SELECT * FROM users WHERE email = $1 LIMIT 1') {
95
+ return { rows: this.users.filter((u) => u.email === values[0]).map((u) => ({ ...u })) }
96
+ }
97
+ // The sign-up's case-blind existence check.
98
+ if (sql === 'SELECT * FROM users WHERE LOWER(email) = LOWER($1) LIMIT 2') {
99
+ const folded = String(values[0]).toLowerCase()
100
+ return {
101
+ rows: this.users
102
+ .filter((u) => String(u.email).toLowerCase() === folded)
103
+ .slice(0, 2)
104
+ .map((u) => ({ ...u })),
105
+ }
106
+ }
107
+ if (sql === 'SELECT * FROM users WHERE id = $1 LIMIT 1') {
108
+ if (this.failLookups) {
109
+ throw Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' })
110
+ }
111
+ return { rows: this.users.filter((u) => u.id === values[0]).map((u) => ({ ...u })) }
112
+ }
113
+ if (sql === 'SELECT id FROM users WHERE email = $1 LIMIT 1') {
114
+ return { rows: this.users.filter((u) => u.email === values[0]).map((u) => ({ id: u.id })) }
115
+ }
116
+ if (sql.startsWith('INSERT INTO users (name, email, password, role, image')) {
117
+ const columns = /INSERT INTO users \(([^)]*)\)/.exec(sql)![1].split(', ')
118
+ const row: Row = { id: 'u' + (this.users.length + 1) }
119
+ columns.forEach((column, index) => {
120
+ row[column] = values[index]
121
+ })
122
+ this.users.push(row)
123
+ return { rows: [{ ...row }] }
124
+ }
125
+ if (sql === 'UPDATE users SET password = $1 WHERE id = $2 AND password = $3') {
126
+ const user = this.users.find((u) => u.id === values[1] && u.password === values[2])
127
+ if (user) {
128
+ user.password = values[0]
129
+ }
130
+ return { rows: [], rowCount: user ? 1 : 0 }
131
+ }
132
+ if (!this.resetsTableExists && sql.includes('teleport_password_resets')) {
133
+ throw Object.assign(new Error('relation "teleport_password_resets" does not exist'), {
134
+ code: '42P01',
135
+ })
136
+ }
137
+ if (sql.startsWith('UPDATE teleport_password_resets SET used_at = NOW() WHERE user_id = $1')) {
138
+ for (const reset of this.resets) {
139
+ if (reset.user_id === values[0] && reset.used_at === null) {
140
+ reset.used_at = this.now
141
+ }
142
+ }
143
+ return { rows: [] }
144
+ }
145
+ if (sql.startsWith('INSERT INTO teleport_password_resets (token_hash, user_id, expires_at)')) {
146
+ this.resets.push({
147
+ token_hash: values[0],
148
+ user_id: values[1],
149
+ expires_at: this.now + Number(values[2]) * 1000,
150
+ used_at: null,
151
+ })
152
+ return { rows: [] }
153
+ }
154
+ if (sql.startsWith('DELETE FROM teleport_password_resets WHERE expires_at <')) {
155
+ return { rows: [] }
156
+ }
157
+ if (sql.startsWith('WITH consumed AS ( UPDATE teleport_password_resets SET used_at = NOW()')) {
158
+ const reset = this.live(values[0])
159
+ if (!reset) {
160
+ return { rows: [] }
161
+ }
162
+ reset.used_at = this.now
163
+ const user = this.users.find((u) => String(u.id) === reset.user_id)
164
+ if (!user) {
165
+ return { rows: [] }
166
+ }
167
+ user.password = values[1]
168
+ return { rows: [{ id: user.id, email: user.email }] }
169
+ }
170
+ if (sql.startsWith('SELECT 1 FROM teleport_password_resets WHERE token_hash = $1')) {
171
+ return { rows: this.live(values[0]) ? [{ '?column?': 1 }] : [] }
172
+ }
173
+ throw new Error('unexpected SQL: ' + sql)
174
+ }
175
+
176
+ private live(tokenHash: string): Reset | undefined {
177
+ return this.resets.find(
178
+ (r) => r.token_hash === tokenHash && r.used_at === null && r.expires_at > this.now
179
+ )
180
+ }
181
+ }
182
+
183
+ const pgFor = (db: FakePostgres) => ({
184
+ Client: class {
185
+ public async connect() {
186
+ return undefined
187
+ }
188
+ public async end() {
189
+ return undefined
190
+ }
191
+ public on() {
192
+ return undefined
193
+ }
194
+ public async query(sql: string, values?: any[]) {
195
+ return db.query(sql, values)
196
+ }
197
+ },
198
+ })
199
+
200
+ const AUTH: UIDLAuthentication = {
201
+ enabled: true,
202
+ dataSourceId: 'ds-1',
203
+ dataSourceType: 'postgresql',
204
+ passwordAuthEnabled: true,
205
+ providers: [],
206
+ roles: ['user', 'admin'],
207
+ tables: {},
208
+ pageProtection: {},
209
+ folderProtection: {},
210
+ authPages: {},
211
+ callbackBaseUrl: '/api/auth/callback',
212
+ envKeys: {},
213
+ customUserProperties: [
214
+ { key: 'company', label: 'Company', columnType: 'TEXT', attributeType: 'string' },
215
+ { key: 'is_admin', label: 'Admin', columnType: 'BOOLEAN', attributeType: 'boolean' },
216
+ { key: 'email_verified', label: 'Verified', columnType: 'BOOLEAN', attributeType: 'boolean' },
217
+ { key: 'credits', label: 'Credits', columnType: 'INTEGER', attributeType: 'number' },
218
+ ],
219
+ } as UIDLAuthentication
220
+
221
+ const ENV = {
222
+ NEXTAUTH_SECRET: 'app-secret',
223
+ DB_CONNECTION_STRING: 'postgres://fake',
224
+ AUTH_PEPPER: 'project-id-1',
225
+ AUTH_PEPPER_SECRET: 'per-project-random-pepper',
226
+ NEXTAUTH_URL: 'https://shop.example',
227
+ RESEND_KEY: 're_test_key',
228
+ }
229
+
230
+ interface FakeResponse {
231
+ statusCode: number
232
+ body: any
233
+ headers: Record<string, string>
234
+ status: (code: number) => FakeResponse
235
+ json: (body: unknown) => FakeResponse
236
+ setHeader: (name: string, value: string) => void
237
+ }
238
+
239
+ const fakeResponse = (): FakeResponse => {
240
+ const res: FakeResponse = {
241
+ statusCode: 0,
242
+ body: undefined,
243
+ headers: {},
244
+ status: (code) => {
245
+ res.statusCode = code
246
+ return res
247
+ },
248
+ json: (body) => {
249
+ res.body = body
250
+ return res
251
+ },
252
+ setHeader: (name, value) => {
253
+ res.headers[name.toLowerCase()] = value
254
+ },
255
+ }
256
+ return res
257
+ }
258
+
259
+ const request = (body: Row, ip = '203.0.113.7', extra: Row = {}) => ({
260
+ method: 'POST',
261
+ headers: { host: 'shop.example' },
262
+ socket: { remoteAddress: ip },
263
+ body,
264
+ query: {},
265
+ ...extra,
266
+ })
267
+
268
+ /**
269
+ * One app instance: the generated modules, required from each other the way
270
+ * the generated files require them.
271
+ */
272
+ const loadApp = (
273
+ options: {
274
+ auth?: UIDLAuthentication
275
+ env?: Record<string, string>
276
+ db?: FakePostgres
277
+ resetPagePath?: string
278
+ tokenInFragment?: boolean
279
+ fetch?: (url: string, init: unknown) => Promise<unknown>
280
+ welcome?: boolean
281
+ /** More modules the generated files may require (a store's driver). */
282
+ externals?: Record<string, unknown>
283
+ } = {}
284
+ ) => {
285
+ const auth = options.auth || AUTH
286
+ const env = { ...(options.env || ENV) }
287
+ const db = options.db || new FakePostgres()
288
+ const sent: Row[] = []
289
+ const delays: number[] = []
290
+ const logs: unknown[][] = []
291
+ const quiet = {
292
+ error: (...args: unknown[]) => logs.push(args),
293
+ warn: (...args: unknown[]) => logs.push(args),
294
+ log: () => undefined,
295
+ info: () => undefined,
296
+ }
297
+ const cache: Record<string, any> = {}
298
+ const sources: Record<string, string> = {
299
+ runtime: generateSecurityRuntimeCode(),
300
+ hash: generateHashPasswordFile(),
301
+ health: generateAuthDbHealthFile(),
302
+ revocation: generateSessionRevocationFile(),
303
+ options: generateAuthOptionsFile(auth, null),
304
+ forgot: generateForgotPasswordRoute({
305
+ resetPagePath: options.resetPagePath || '/reset-password',
306
+ tokenInFragment: options.tokenInFragment,
307
+ emailProvider: 'resend',
308
+ fromEmail: 'shop@brand.test',
309
+ emailSecretEnvName: 'RESEND_KEY',
310
+ emailSubject: 'Reset your password',
311
+ emailBodyHtml: '<a href="{{resetUrl}}">Reset</a> for {{recipientEmail}}',
312
+ }),
313
+ reset: generateResetPasswordRoute(),
314
+ signup: generateSignupRouteFile(
315
+ auth,
316
+ options.welcome
317
+ ? {
318
+ emailProvider: 'resend',
319
+ fromEmail: 'shop@brand.test',
320
+ emailSecretEnvName: 'RESEND_KEY',
321
+ emailSubject: 'Welcome',
322
+ emailBodyHtml: '<p>Welcome {{userName}}</p>',
323
+ siteName: 'Shop',
324
+ }
325
+ : {},
326
+ {},
327
+ {
328
+ fields: resolveSignupFields(auth, [{ email: 1, password: 1, company: 1, is_admin: 1 }]),
329
+ }
330
+ ),
331
+ nextauth: generateNextAuthRouteFile({ passwordAuthEnabled: true }),
332
+ }
333
+ const fakeNextAuth = (authOptions: Row) => async (req: Row, res: FakeResponse) => {
334
+ try {
335
+ const user = await authOptions.providers[0].authorize(req.body, {
336
+ headers: req.headers,
337
+ body: req.body,
338
+ })
339
+ res.status(200).json({ url: 'https://shop.example/', user })
340
+ } catch (err) {
341
+ res
342
+ .status(401)
343
+ .json({ url: 'https://shop.example/api/auth/error?error=' + (err as Error).message })
344
+ }
345
+ }
346
+ const external: Record<string, unknown> = {
347
+ crypto: nodeCrypto,
348
+ net,
349
+ pg: pgFor(db),
350
+ bcryptjs: fakeBcrypt,
351
+ 'next-auth': fakeNextAuth,
352
+ 'next-auth/providers/credentials': (config: unknown) => config,
353
+ resend: {
354
+ Resend: class {
355
+ public emails = {
356
+ send: async (msg: Row) => {
357
+ sent.push(msg)
358
+ return {}
359
+ },
360
+ }
361
+ },
362
+ },
363
+ '../../../utils/email/email-locale': {
364
+ resolveRequestLocale: () => '',
365
+ pickLocalizedTemplate: (base: Row) => base,
366
+ },
367
+ '../../../utils/email/sent-email-log': {
368
+ recordSentEmail: (entry: Row) => sent.push({ ledger: entry }),
369
+ settleSentEmailLog: async () => undefined,
370
+ },
371
+ ...(options.externals || {}),
372
+ }
373
+ const files: Record<string, string> = {
374
+ runtime: 'runtime',
375
+ '../security-runtime': 'runtime',
376
+ '../../../utils/security-runtime': 'runtime',
377
+ './hash-password': 'hash',
378
+ '../../../utils/auth/hash-password': 'hash',
379
+ './db-health': 'health',
380
+ './session-revocation': 'revocation',
381
+ './auth-options': 'options',
382
+ '../../../utils/auth/auth-options': 'options',
383
+ }
384
+ const load = (name: string): any => {
385
+ if (cache[name]) {
386
+ return cache[name].exports
387
+ }
388
+ const module = { exports: {} as any }
389
+ cache[name] = module
390
+ const req = (id: string): unknown => {
391
+ if (Object.prototype.hasOwnProperty.call(files, id)) {
392
+ return load(files[id])
393
+ }
394
+ if (Object.prototype.hasOwnProperty.call(external, id)) {
395
+ return external[id]
396
+ }
397
+ throw new Error(name + ' required an unexpected module: ' + id)
398
+ }
399
+ const setTimeoutShim = (fn: () => void, ms: number) => {
400
+ delays.push(ms)
401
+ fn()
402
+ return 0
403
+ }
404
+ // eslint-disable-next-line no-new-func
405
+ new Function(
406
+ 'module',
407
+ 'exports',
408
+ 'require',
409
+ 'process',
410
+ 'console',
411
+ 'Buffer',
412
+ 'setTimeout',
413
+ 'fetch',
414
+ sources[name]
415
+ )(
416
+ module,
417
+ module.exports,
418
+ req,
419
+ { env },
420
+ quiet,
421
+ Buffer,
422
+ setTimeoutShim,
423
+ options.fetch || (async () => ({ ok: false }))
424
+ )
425
+ return module.exports
426
+ }
427
+ return { db, env, sent, delays, logs, load, sources }
428
+ }
429
+
430
+ const VICTIM = { id: 'u-victim', email: 'victim@site.test', name: 'Vic', role: 'admin' }
431
+
432
+ const tokenFromEmail = (sent: Row[]): string => {
433
+ const mail = sent.find((entry) => entry.to)
434
+ const match = /token=([0-9a-f]+)/.exec(String(mail && mail.html))
435
+ return match ? match[1] : ''
436
+ }
437
+
438
+ describe('A3-C1: forgot / reset password are decided on the server', () => {
439
+ const victimDb = () => {
440
+ const db = new FakePostgres()
441
+ db.users.push({ ...VICTIM, password: pepperedHash('Original#1', ENV.AUTH_PEPPER_SECRET) })
442
+ return db
443
+ }
444
+
445
+ it('refuses the proof-of-concept: a token the requester chose never becomes a reset link', async () => {
446
+ const app = loadApp({ db: victimDb() })
447
+ const forgot = app.load('forgot')
448
+ const reset = app.load('reset')
449
+
450
+ // Step 1 of the PoC, sent to the new route: the forged workflow context rides along.
451
+ const res1 = fakeResponse()
452
+ await forgot(
453
+ request({
454
+ email: VICTIM.email,
455
+ token: 'ATTACKER-CHOSEN',
456
+ context: { genToken: { value: 'ATTACKER-CHOSEN' }, extract: { email: VICTIM.email } },
457
+ }),
458
+ res1
459
+ )
460
+ expect(res1.statusCode).toBe(200)
461
+ expect(app.db.resets).toHaveLength(1)
462
+ expect(app.db.resets[0].token_hash).not.toBe(sha256('ATTACKER-CHOSEN'))
463
+ expect(app.db.resets[0].token_hash).not.toContain('ATTACKER')
464
+ expect(app.db.resets[0].user_id).toBe(VICTIM.id)
465
+
466
+ // Step 2 of the PoC: the chosen token (and a well-formed one) sets nothing.
467
+ for (const token of ['ATTACKER-CHOSEN', 'a'.repeat(64)]) {
468
+ const res2 = fakeResponse()
469
+ await reset(request({ token, password: 'Pwned1!xx' }), res2)
470
+ expect(res2.statusCode).toBe(200)
471
+ expect(res2.body).toEqual({ valid: false, changed: false })
472
+ }
473
+ expect(app.db.users[0].password).toBe(pepperedHash('Original#1', ENV.AUTH_PEPPER_SECRET))
474
+ })
475
+
476
+ it('mints a random token, stores only its SHA-256 and mails it only to the account address', async () => {
477
+ const app = loadApp({ db: victimDb(), resetPagePath: '/account/new-password' })
478
+ await app.load('forgot')(
479
+ request({ email: VICTIM.email }, '198.51.100.1', {
480
+ headers: { host: 'attacker.example' },
481
+ }),
482
+ fakeResponse()
483
+ )
484
+ const mails = app.sent.filter((entry) => entry.to)
485
+ expect(mails).toHaveLength(1)
486
+ expect(mails[0].to).toEqual([VICTIM.email])
487
+ const token = tokenFromEmail(app.sent)
488
+ expect(token).toMatch(/^[0-9a-f]{64}$/)
489
+ expect(app.db.resets[0].token_hash).toBe(sha256(token))
490
+ // The link's host is the configured one, never the Host header a request sent.
491
+ expect(mails[0].html).toContain('https://shop.example/account/new-password?token=' + token)
492
+ expect(mails[0].html).not.toContain('attacker.example')
493
+ // The sent-email log never keeps the token.
494
+ const ledger = app.sent.find((entry) => entry.ledger)!.ledger
495
+ expect(JSON.stringify(ledger)).not.toContain(token)
496
+ expect(ledger.html).toContain('token=[hidden]')
497
+ })
498
+
499
+ it('a link works once, for its own account, within 30 minutes', async () => {
500
+ const db = victimDb()
501
+ db.users.push({ id: 'u-other', email: 'other@site.test', password: 'x', role: 'user' })
502
+ const app = loadApp({ db })
503
+ await app.load('forgot')(request({ email: VICTIM.email }), fakeResponse())
504
+ const token = tokenFromEmail(app.sent)
505
+ const reset = app.load('reset')
506
+
507
+ const check = fakeResponse()
508
+ await reset(request({ token, checkOnly: true }), check)
509
+ expect(check.body).toEqual({ valid: true, changed: false })
510
+
511
+ const first = fakeResponse()
512
+ // A user id or email in the body changes nothing: the link is bound to its account.
513
+ await reset(
514
+ request({ token, password: 'NewPassw0rd', userId: 'u-other', email: 'other@site.test' }),
515
+ first
516
+ )
517
+ expect(first.body).toEqual({ valid: true, changed: true })
518
+ expect(db.users[0].password).toBe(pepperedHash('NewPassw0rd', ENV.AUTH_PEPPER_SECRET))
519
+ expect(db.users[1].password).toBe('x')
520
+
521
+ const second = fakeResponse()
522
+ await reset(request({ token, password: 'Another0ne' }), second)
523
+ expect(second.body).toEqual({ valid: false, changed: false })
524
+
525
+ // A fresh link, 31 minutes later.
526
+ app.sent.length = 0
527
+ await app.load('forgot')(request({ email: VICTIM.email }, '198.51.100.2'), fakeResponse())
528
+ const later = tokenFromEmail(app.sent)
529
+ db.now += 31 * 60 * 1000
530
+ const expired = fakeResponse()
531
+ await reset(request({ token: later, password: 'Late123456' }), expired)
532
+ expect(expired.body).toEqual({ valid: false, changed: false })
533
+ })
534
+
535
+ it('a new link ends the one sent before it', async () => {
536
+ const app = loadApp({ db: victimDb() })
537
+ await app.load('forgot')(request({ email: VICTIM.email }), fakeResponse())
538
+ const first = tokenFromEmail(app.sent)
539
+ app.sent.length = 0
540
+ await app.load('forgot')(request({ email: VICTIM.email }), fakeResponse())
541
+ const res = fakeResponse()
542
+ await app.load('reset')(request({ token: first, checkOnly: true }), res)
543
+ expect(res.body).toEqual({ valid: false, changed: false })
544
+ })
545
+
546
+ it('refuses a new password outside the policy (8 characters, 128 bytes)', async () => {
547
+ const app = loadApp({ db: victimDb() })
548
+ await app.load('forgot')(request({ email: VICTIM.email }), fakeResponse())
549
+ const token = tokenFromEmail(app.sent)
550
+ for (const password of ['short7!', 'é'.repeat(65)]) {
551
+ const refused = fakeResponse()
552
+ await app.load('reset')(request({ token, password }), refused)
553
+ expect(refused.statusCode).toBe(400)
554
+ }
555
+ const res = fakeResponse()
556
+ await app.load('reset')(request({ token, checkOnly: true }), res)
557
+ expect(res.body.valid).toBe(true)
558
+ })
559
+
560
+ it('answers an address without an account exactly like one with, and writes nothing for it', async () => {
561
+ const app = loadApp({ db: victimDb() })
562
+ const known = fakeResponse()
563
+ await app.load('forgot')(request({ email: VICTIM.email }), known)
564
+ const delaysForKnown = app.delays.slice()
565
+ app.delays.length = 0
566
+ const unknown = fakeResponse()
567
+ await app.load('forgot')(request({ email: 'nobody@site.test' }), unknown)
568
+
569
+ expect(unknown.statusCode).toBe(known.statusCode)
570
+ expect(unknown.body).toEqual(known.body)
571
+ expect(unknown.body).toEqual({ success: true, message: FORGOT_PASSWORD_ANSWER })
572
+ expect(app.db.resets).toHaveLength(1)
573
+ expect(app.sent.filter((entry) => entry.to)).toHaveLength(1)
574
+ // Both wait out the same minimum response time.
575
+ expect(delaysForKnown.length).toBeGreaterThan(0)
576
+ expect(app.delays.length).toBeGreaterThan(0)
577
+ })
578
+
579
+ it('sends one address at most 3 links an hour, answering the same', async () => {
580
+ const app = loadApp({ db: victimDb() })
581
+ for (let i = 0; i < 5; i++) {
582
+ const res = fakeResponse()
583
+ await app.load('forgot')(request({ email: VICTIM.email }, '198.51.100.' + (i + 10)), res)
584
+ expect(res.body).toEqual({ success: true, message: FORGOT_PASSWORD_ANSWER })
585
+ }
586
+ expect(app.sent.filter((entry) => entry.to)).toHaveLength(3)
587
+ })
588
+
589
+ it('keeps the reset page a same-site path and never takes it from a request', () => {
590
+ expect(resolveResetPagePath('/account/new-password')).toBe('/account/new-password')
591
+ for (const bad of [
592
+ '//evil.test/x',
593
+ 'https://evil.test',
594
+ '/go?url=https://evil.test',
595
+ '/a/../b',
596
+ '',
597
+ ]) {
598
+ expect(resolveResetPagePath(bad)).toBe('/reset-password')
599
+ }
600
+ })
601
+ })
602
+
603
+ describe('A3-H1: sign-in and sign-up limits', () => {
604
+ const signIn = async (
605
+ app: ReturnType<typeof loadApp>,
606
+ email: string,
607
+ password: string,
608
+ ip: string
609
+ ) => {
610
+ const res = fakeResponse()
611
+ await app.load('nextauth')(
612
+ request({ email, password, json: 'true' }, ip, {
613
+ query: { nextauth: ['callback', 'credentials'] },
614
+ headers: { host: 'shop.example', 'x-teleport-signin-visitor': '10.9.9.9' },
615
+ }),
616
+ res
617
+ )
618
+ return res
619
+ }
620
+
621
+ it(`answers 429 after ${SIGN_IN_FAILURE_LIMIT} failures for one email from one visitor`, async () => {
622
+ const db = new FakePostgres()
623
+ db.users.push({ ...VICTIM, password: pepperedHash('Right#Pass1', ENV.AUTH_PEPPER_SECRET) })
624
+ const app = loadApp({ db })
625
+ for (let i = 0; i < SIGN_IN_FAILURE_LIMIT; i++) {
626
+ const res = await signIn(app, VICTIM.email, 'wrong-' + i, '203.0.113.50')
627
+ expect(res.statusCode).toBe(401)
628
+ expect(res.body.url).toContain('error=InvalidCredentials')
629
+ }
630
+ const refused = await signIn(app, VICTIM.email, 'Right#Pass1', '203.0.113.50')
631
+ expect(refused.statusCode).toBe(429)
632
+ expect(refused.body.url).toContain('error=TooManyAttempts')
633
+ expect(Number(refused.headers['retry-after'])).toBeGreaterThan(0)
634
+
635
+ // Another visitor, or another email from the same visitor, is not affected.
636
+ expect((await signIn(app, VICTIM.email, 'Right#Pass1', '203.0.113.51')).statusCode).toBe(200)
637
+ expect((await signIn(app, 'someone@site.test', 'x', '203.0.113.50')).statusCode).toBe(401)
638
+ })
639
+
640
+ it('does not count successful sign-ins as failures', async () => {
641
+ const db = new FakePostgres()
642
+ db.users.push({ ...VICTIM, password: pepperedHash('Right#Pass1', ENV.AUTH_PEPPER_SECRET) })
643
+ const app = loadApp({ db })
644
+ for (let i = 0; i < SIGN_IN_FAILURE_LIMIT + 5; i++) {
645
+ expect((await signIn(app, VICTIM.email, 'Right#Pass1', '203.0.113.60')).statusCode).toBe(200)
646
+ }
647
+ })
648
+
649
+ it(`answers 429 once one visitor has ${SIGN_IN_VISITOR_FAILURE_LIMIT} failures across any emails`, async () => {
650
+ const app = loadApp()
651
+ for (let i = 0; i < SIGN_IN_VISITOR_FAILURE_LIMIT; i++) {
652
+ // A different address each time: the per-email limit never trips.
653
+ const res = await signIn(app, `user${i}@site.test`, 'guess', '203.0.113.90')
654
+ expect(res.statusCode).toBe(401)
655
+ }
656
+ const refused = await signIn(app, 'fresh@site.test', 'guess', '203.0.113.90')
657
+ expect(refused.statusCode).toBe(429)
658
+ expect(refused.body.url).toContain('error=TooManyAttempts')
659
+ expect((await signIn(app, 'fresh@site.test', 'guess', '203.0.113.91')).statusCode).toBe(401)
660
+ })
661
+
662
+ it('limits sign-ups to 10 an hour per visitor', async () => {
663
+ const app = loadApp()
664
+ for (let i = 0; i < 10; i++) {
665
+ const created = fakeResponse()
666
+ await app.load('signup')(
667
+ request({ email: `a${i}@site.test`, password: 'Passw0rd!' }, '203.0.113.70'),
668
+ created
669
+ )
670
+ expect(created.statusCode).toBe(201)
671
+ }
672
+ const res = fakeResponse()
673
+ await app.load('signup')(
674
+ request({ email: 'a11@site.test', password: 'Passw0rd!' }, '203.0.113.70'),
675
+ res
676
+ )
677
+ expect(res.statusCode).toBe(429)
678
+ })
679
+ })
680
+
681
+ describe('A3-M3 / A1-05: sign-up writes only what its form collects', () => {
682
+ it('ignores email_verified, is_admin, role and credits in the body', async () => {
683
+ const app = loadApp()
684
+ const res = fakeResponse()
685
+ await app.load('signup')(
686
+ request({
687
+ email: 'new@site.test',
688
+ password: 'Passw0rd!',
689
+ name: 'New',
690
+ company: 'ACME',
691
+ is_admin: true,
692
+ email_verified: true,
693
+ role: 'admin',
694
+ credits: 9999,
695
+ }),
696
+ res
697
+ )
698
+ expect(res.statusCode).toBe(201)
699
+ const row = app.db.users[0]
700
+ expect(row).toMatchObject({ email: 'new@site.test', company: 'ACME', role: 'user' })
701
+ expect(row.is_admin).toBeNull()
702
+ expect(row.email_verified).toBeNull()
703
+ expect(row.credits).toBeNull()
704
+ })
705
+
706
+ it('collects only declared, non-privileged fields of the sign-up form', () => {
707
+ expect(
708
+ resolveSignupFields(AUTH, [{ email: 1, company: 1, is_admin: 1, credits: 1, undeclared: 1 }])
709
+ ).toEqual(['image', 'company'])
710
+ for (const key of [
711
+ 'role',
712
+ 'roleName',
713
+ 'is_admin',
714
+ 'isAdmin',
715
+ 'admin',
716
+ 'is_staff',
717
+ 'isSuperuser',
718
+ 'email_verified',
719
+ 'emailVerified',
720
+ 'verified',
721
+ 'credits',
722
+ 'balance',
723
+ 'plan',
724
+ 'tier',
725
+ 'subscription_tier',
726
+ 'entitlements',
727
+ ]) {
728
+ expect(isPrivilegedUserField(key)).toBe(true)
729
+ }
730
+ expect(isPrivilegedUserField('company')).toBe(false)
731
+ })
732
+
733
+ it('refuses a password shorter than 8 characters', async () => {
734
+ const app = loadApp()
735
+ const res = fakeResponse()
736
+ await app.load('signup')(request({ email: 'p@site.test', password: 'seven77' }), res)
737
+ expect(res.statusCode).toBe(400)
738
+ expect(app.db.users).toHaveLength(0)
739
+ })
740
+ })
741
+
742
+ describe('R19 / A8-09: the project pepper, with the old one still accepted', () => {
743
+ const signInWith = async (env: Record<string, string>, stored: string, password: string) => {
744
+ const db = new FakePostgres()
745
+ db.users.push({ ...VICTIM, password: stored })
746
+ const app = loadApp({ db, env })
747
+ const options = app.load('options')
748
+ const visitor = { headers: { 'x-teleport-signin-visitor': '203.0.113.80' } }
749
+ let error: Error | null = null
750
+ let user: Row | null = null
751
+ try {
752
+ user = await options.providers[0].authorize({ email: VICTIM.email, password }, visitor)
753
+ } catch (err) {
754
+ error = err as Error
755
+ }
756
+ return { db, user, error, app }
757
+ }
758
+
759
+ it('signs a legacy-pepper user in and re-hashes the password with the project pepper', async () => {
760
+ const legacy = pepperedHash('Old#Passw0rd', ENV.AUTH_PEPPER)
761
+ const first = await signInWith(ENV, legacy, 'Old#Passw0rd')
762
+ expect(first.error).toBeNull()
763
+ expect(first.user).toMatchObject({ id: VICTIM.id, role: 'admin' })
764
+ expect(first.db.users[0].password).toBe(pepperedHash('Old#Passw0rd', ENV.AUTH_PEPPER_SECRET))
765
+ // The session is stamped with the account as stored after the re-hash.
766
+ const revocation = first.app.load('revocation')
767
+ expect(revocation.stampVerdict(first.user, first.db.users[0])).toBe('current')
768
+ })
769
+
770
+ it('keeps hashing and verifying on the old pepper when the project has no pepper secret', async () => {
771
+ const env = { ...ENV, AUTH_PEPPER_SECRET: '' }
772
+ const legacy = pepperedHash('Old#Passw0rd', ENV.AUTH_PEPPER)
773
+ const result = await signInWith(env, legacy, 'Old#Passw0rd')
774
+ expect(result.error).toBeNull()
775
+ expect(result.db.users[0].password).toBe(legacy)
776
+ const hash = result.app.load('hash')
777
+ expect(hash.hashPassword('Brand#New1')).toBe(pepperedHash('Brand#New1', ENV.AUTH_PEPPER))
778
+ // An unresolved placeholder is no secret either.
779
+ const placeholder = loadApp({
780
+ env: { ...ENV, AUTH_PEPPER_SECRET: 'teleporthq.secrets.AUTH_PEPPER_SECRET' },
781
+ })
782
+ expect(placeholder.load('hash').hashPassword('Brand#New1')).toBe(
783
+ pepperedHash('Brand#New1', ENV.AUTH_PEPPER)
784
+ )
785
+ })
786
+
787
+ it('refuses a wrong password under either pepper', async () => {
788
+ const result = await signInWith(ENV, pepperedHash('Old#Passw0rd', ENV.AUTH_PEPPER), 'nope-nope')
789
+ expect(result.error && result.error.message).toBe('InvalidCredentials')
790
+ })
791
+
792
+ it('makes the workflow hash / compare nodes use the same two peppers', async () => {
793
+ const run = (source: string, config: Row, env: Record<string, string>) =>
794
+ // eslint-disable-next-line no-new-func
795
+ new Function(
796
+ 'require',
797
+ 'globalThis',
798
+ `${source}\nreturn ${source.match(/function (\w+)/)![1]};`
799
+ )((id: string) => (id === 'bcryptjs' ? fakeBcrypt : nodeCrypto), {
800
+ process: { env },
801
+ Buffer,
802
+ })(config, {})
803
+ const hashed = await run(accountHashPassword.generateHandler(), { password: 'Node#Pass1' }, ENV)
804
+ expect(hashed.hash).toBe(pepperedHash('Node#Pass1', ENV.AUTH_PEPPER_SECRET))
805
+ await expect(
806
+ run(accountHashPassword.generateHandler(), { password: 'seven77' }, ENV)
807
+ ).rejects.toThrow('at least 8')
808
+ const legacy = pepperedHash('Node#Pass1', ENV.AUTH_PEPPER)
809
+ const compared = await run(
810
+ accountComparePasswords.generateHandler(),
811
+ { password: 'Node#Pass1', hash: legacy },
812
+ ENV
813
+ )
814
+ expect(compared.isMatch).toBe(true)
815
+ })
816
+ })
817
+
818
+ describe('A3-H2: an OAuth sign-in takes a local account only with a verified email', () => {
819
+ const OAUTH_AUTH = {
820
+ ...AUTH,
821
+ providers: [{ id: 'keycloak', name: 'Keycloak', credentials: {} }],
822
+ } as UIDLAuthentication
823
+
824
+ const app = () => {
825
+ const db = new FakePostgres()
826
+ db.users.push({ ...VICTIM, password: 'x' })
827
+ return loadApp({ auth: OAUTH_AUTH, db })
828
+ }
829
+
830
+ it('keeps an unverified email out of the local admin account', async () => {
831
+ const instance = app()
832
+ const options = instance.load('options')
833
+ const token: Row = { email: VICTIM.email, name: 'Eve', sub: 'kc-1' }
834
+ await options.callbacks.jwt({
835
+ token,
836
+ user: { id: 'kc-1', email: VICTIM.email, name: 'Eve' },
837
+ account: { provider: 'keycloak', type: 'oauth', providerAccountId: 'kc-1' },
838
+ profile: { email: VICTIM.email },
839
+ })
840
+ expect(token.email).toBeUndefined()
841
+ expect(token.role).toBeUndefined()
842
+ expect(token.id).toBe('oauth:keycloak:kc-1')
843
+ instance.db.statements.length = 0
844
+ await options.callbacks.jwt({ token, trigger: 'update' })
845
+ expect(token.role).toBeUndefined()
846
+ expect(token.id).toBe('oauth:keycloak:kc-1')
847
+ expect(instance.db.statements).toEqual([])
848
+ const session = await options.callbacks.session({ session: { user: {} }, token })
849
+ expect(session.user).not.toHaveProperty('__oauthUnverified')
850
+ })
851
+
852
+ it('binds a verified email (or a provider that only hands out verified ones) to the account', async () => {
853
+ for (const [provider, profile] of [
854
+ ['keycloak', { email: VICTIM.email, email_verified: true }],
855
+ ['google', { email: VICTIM.email }],
856
+ ] as Array<[string, Row]>) {
857
+ const options = app().load('options')
858
+ const token: Row = { email: VICTIM.email, sub: 'p-1' }
859
+ await options.callbacks.jwt({
860
+ token,
861
+ user: { id: 'p-1', email: VICTIM.email },
862
+ account: { provider, type: 'oauth', providerAccountId: 'p-1' },
863
+ profile,
864
+ })
865
+ await options.callbacks.jwt({ token, trigger: 'update' })
866
+ expect(token).toMatchObject({ id: VICTIM.id, role: 'admin', email: VICTIM.email })
867
+ }
868
+ })
869
+
870
+ it('I1: a multi-tenant Microsoft sign-in never takes the local account', async () => {
871
+ const auth = {
872
+ ...AUTH,
873
+ providers: [
874
+ {
875
+ id: 'azure-ad',
876
+ name: 'Azure AD',
877
+ credentials: { AUTH_AZURE_AD_ID: '', AUTH_AZURE_AD_SECRET: '' },
878
+ },
879
+ ],
880
+ } as UIDLAuthentication
881
+ const db = new FakePostgres()
882
+ db.users.push({ ...VICTIM, password: 'x' })
883
+ const options = loadApp({ auth, db }).load('options')
884
+ const token: Row = { email: VICTIM.email, sub: 'ms-1' }
885
+ await options.callbacks.jwt({
886
+ token,
887
+ user: { id: 'ms-1', email: VICTIM.email },
888
+ account: { provider: 'azure-ad', type: 'oauth', providerAccountId: 'ms-1' },
889
+ profile: { email: VICTIM.email, tid: '11111111-2222-3333-4444-555555555555' },
890
+ })
891
+ await options.callbacks.jwt({ token, trigger: 'update' })
892
+ expect(token.email).toBeUndefined()
893
+ expect(token.role).toBeUndefined()
894
+ expect(token.id).toBe('oauth:azure-ad:ms-1')
895
+ })
896
+
897
+ it('I1: a single-tenant Microsoft sign-in (or a verified email domain) takes the local account', async () => {
898
+ const TENANT = '11111111-2222-3333-4444-555555555555'
899
+ const auth = {
900
+ ...AUTH,
901
+ providers: [
902
+ {
903
+ id: 'azure-ad',
904
+ name: 'Azure AD',
905
+ credentials: {
906
+ AUTH_AZURE_AD_ID: '',
907
+ AUTH_AZURE_AD_SECRET: '',
908
+ AUTH_AZURE_AD_TENANT_ID: '',
909
+ },
910
+ },
911
+ ],
912
+ } as UIDLAuthentication
913
+ const signIn = async (env: Record<string, string>, profile: Row) => {
914
+ const db = new FakePostgres()
915
+ db.users.push({ ...VICTIM, password: 'x' })
916
+ const options = loadApp({ auth, db, env: { ...ENV, ...env } }).load('options')
917
+ const token: Row = { email: VICTIM.email, sub: 'ms-1' }
918
+ await options.callbacks.jwt({
919
+ token,
920
+ user: { id: 'ms-1', email: VICTIM.email },
921
+ account: { provider: 'azure-ad', type: 'oauth', providerAccountId: 'ms-1' },
922
+ profile,
923
+ })
924
+ await options.callbacks.jwt({ token, trigger: 'update' })
925
+ return token
926
+ }
927
+ // Configured for the owner's own tenant: adopted.
928
+ expect(
929
+ await signIn({ AUTH_AZURE_AD_TENANT_ID: TENANT }, { email: VICTIM.email, tid: TENANT })
930
+ ).toMatchObject({
931
+ id: VICTIM.id,
932
+ role: 'admin',
933
+ })
934
+ // A token from another tenant, or a "common" configuration: not adopted.
935
+ expect(
936
+ (
937
+ await signIn(
938
+ { AUTH_AZURE_AD_TENANT_ID: TENANT },
939
+ { email: VICTIM.email, tid: '99999999-2222-3333-4444-555555555555' }
940
+ )
941
+ ).role
942
+ ).toBeUndefined()
943
+ expect(
944
+ (await signIn({ AUTH_AZURE_AD_TENANT_ID: 'common' }, { email: VICTIM.email })).role
945
+ ).toBeUndefined()
946
+ // Multi-tenant, but the token says the email's domain is verified: adopted.
947
+ expect((await signIn({}, { email: VICTIM.email, xms_edov: true })).role).toBe('admin')
948
+ // The provider really is configured for that tenant.
949
+ expect(generateAuthOptionsFile(auth, null)).toContain(
950
+ 'tenantId: process.env.AUTH_AZURE_AD_TENANT_ID || undefined'
951
+ )
952
+ })
953
+
954
+ it('trusts an explicit "not verified" over the provider list, and asks GitHub', async () => {
955
+ const google = app().load('options')
956
+ const token: Row = { email: VICTIM.email, sub: 'g-1' }
957
+ await google.callbacks.jwt({
958
+ token,
959
+ user: { id: 'g-1', email: VICTIM.email },
960
+ account: { provider: 'google', type: 'oauth', providerAccountId: 'g-1' },
961
+ profile: { email: VICTIM.email, email_verified: false },
962
+ })
963
+ expect(token.email).toBeUndefined()
964
+
965
+ const asked: string[] = []
966
+ const github = loadApp({
967
+ auth: OAUTH_AUTH,
968
+ fetch: async (url: string) => {
969
+ asked.push(url)
970
+ return {
971
+ ok: true,
972
+ json: async () => [{ email: VICTIM.email, verified: false, primary: true }],
973
+ }
974
+ },
975
+ }).load('options')
976
+ const ghToken: Row = { email: VICTIM.email, sub: 'gh-1' }
977
+ await github.callbacks.jwt({
978
+ token: ghToken,
979
+ user: { id: 'gh-1', email: VICTIM.email },
980
+ account: { provider: 'github', type: 'oauth', providerAccountId: 'gh-1', access_token: 't' },
981
+ profile: { email: VICTIM.email },
982
+ })
983
+ expect(asked).toEqual(['https://api.github.com/user/emails'])
984
+ expect(ghToken.email).toBeUndefined()
985
+ })
986
+ })
987
+
988
+ describe('A3-M1 / R16: a password change ends every session, in every check', () => {
989
+ it('revokes a stamped session after a password reset — in the jwt callback and the shared guard check', async () => {
990
+ const db = new FakePostgres()
991
+ db.users.push({ ...VICTIM, password: pepperedHash('Right#Pass1', ENV.AUTH_PEPPER_SECRET) })
992
+ const app = loadApp({ db })
993
+ const options = app.load('options')
994
+ const user = await options.providers[0].authorize(
995
+ { email: VICTIM.email, password: 'Right#Pass1' },
996
+ { headers: {} }
997
+ )
998
+ const token: Row = { email: VICTIM.email, sub: VICTIM.id }
999
+ await options.callbacks.jwt({ token, user })
1000
+ expect(typeof token.__sessionStamp).toBe('string')
1001
+ await options.callbacks.jwt({ token, trigger: 'update' })
1002
+ const revocation = app.load('revocation')
1003
+ expect(await revocation.liveSessionToken(token)).not.toBeNull()
1004
+ const session = await options.callbacks.session({ session: { user: {} }, token })
1005
+ expect(session.user).not.toHaveProperty('__sessionStamp')
1006
+
1007
+ // The password is reset (another instance: no cached verdict).
1008
+ await app.load('forgot')(request({ email: VICTIM.email }), fakeResponse())
1009
+ await app.load('reset')(
1010
+ request({ token: tokenFromEmail(app.sent), password: 'Brand#New2' }),
1011
+ fakeResponse()
1012
+ )
1013
+
1014
+ await expect(options.callbacks.jwt({ token, trigger: 'update' })).rejects.toThrow(
1015
+ 'SessionRevoked'
1016
+ )
1017
+ const other = loadApp({ db })
1018
+ expect(await other.load('revocation').liveSessionToken(token)).toBeNull()
1019
+ })
1020
+
1021
+ // I2: the check lives in the shared reader (__tqSessionToken), so every
1022
+ // consumer refuses a revoked session — proven here through two of them.
1023
+ const stamped = { id: VICTIM.id, email: VICTIM.email, role: 'admin', __sessionStamp: 'stamp-1' }
1024
+ const revocationStub = (revokedStamp: boolean) => ({
1025
+ liveSessionToken: async (token: Row) =>
1026
+ revokedStamp && token.__sessionStamp === 'stamp-1' ? null : token,
1027
+ })
1028
+
1029
+ it('the account-deletion route refuses a revoked session and deletes nothing', async () => {
1030
+ const run = async (revoked: boolean) => {
1031
+ const statements: string[] = []
1032
+ const module = { exports: {} as any }
1033
+ // eslint-disable-next-line no-new-func
1034
+ new Function(
1035
+ 'require',
1036
+ 'module',
1037
+ 'exports',
1038
+ 'process',
1039
+ 'console',
1040
+ generateAccountDeleteRoute({})
1041
+ )(
1042
+ (id: string) => {
1043
+ if (id === 'pg') {
1044
+ return {
1045
+ Client: class {
1046
+ public async connect() {
1047
+ return undefined
1048
+ }
1049
+ public async end() {
1050
+ return undefined
1051
+ }
1052
+ public async query(sql: string) {
1053
+ statements.push(sql)
1054
+ return sql.startsWith('SELECT * FROM users')
1055
+ ? { rows: [{ ...VICTIM }] }
1056
+ : { rows: [] }
1057
+ }
1058
+ },
1059
+ }
1060
+ }
1061
+ if (id === 'next-auth/jwt') {
1062
+ return { getToken: async () => stamped }
1063
+ }
1064
+ if (id === '../../../utils/auth/session-revocation') {
1065
+ return revocationStub(revoked)
1066
+ }
1067
+ if (id === '../../../utils/email/email-locale') {
1068
+ return { resolveRequestLocale: () => '' }
1069
+ }
1070
+ throw new Error('unexpected require: ' + id)
1071
+ },
1072
+ module,
1073
+ module.exports,
1074
+ { env: { NEXTAUTH_SECRET: 's', TELEPORT_DB_CONNECTION_STRING: 'postgres://x' } },
1075
+ { error: () => undefined, warn: () => undefined, log: () => undefined }
1076
+ )
1077
+ const res = fakeResponse()
1078
+ await module.exports(
1079
+ {
1080
+ method: 'POST',
1081
+ headers: { host: 'shop.example', cookie: '__Host-next-auth.session-token=mine' },
1082
+ },
1083
+ res
1084
+ )
1085
+ return { res, statements }
1086
+ }
1087
+ const refused = await run(true)
1088
+ expect(refused.res.statusCode).toBe(401)
1089
+ expect(refused.statements.filter((sql) => sql.startsWith('DELETE'))).toEqual([])
1090
+ const live = await run(false)
1091
+ expect(live.res.statusCode).toBe(200)
1092
+ expect(live.statements.some((sql) => sql.startsWith('DELETE FROM users'))).toBe(true)
1093
+ })
1094
+
1095
+ it('the workflow route guard treats a revoked session as anonymous', async () => {
1096
+ const boot = (revoked: boolean) => {
1097
+ const module = { exports: {} as any }
1098
+ // eslint-disable-next-line no-new-func
1099
+ new Function(
1100
+ 'require',
1101
+ 'module',
1102
+ 'exports',
1103
+ 'process',
1104
+ 'Buffer',
1105
+ generateWorkflowAuthHelperFile({})
1106
+ )(
1107
+ (id: string) => {
1108
+ if (id === 'next-auth/jwt') {
1109
+ return { getToken: async () => stamped }
1110
+ }
1111
+ if (id === '../../utils/auth/session-revocation') {
1112
+ return revocationStub(revoked)
1113
+ }
1114
+ if (id === './server-runtime') {
1115
+ return { isInternalCall: () => false }
1116
+ }
1117
+ if (id === 'crypto') {
1118
+ return nodeCrypto
1119
+ }
1120
+ throw new Error('unexpected require: ' + id)
1121
+ },
1122
+ module,
1123
+ module.exports,
1124
+ { env: { NEXTAUTH_SECRET: 's' } },
1125
+ Buffer
1126
+ )
1127
+ return module.exports.guardWorkflowRequest
1128
+ }
1129
+ const req = { headers: { host: 'shop.example', cookie: '__Host-next-auth.session-token=mine' } }
1130
+ const policy = { requiresAuth: true, allowedRoles: ['admin'] }
1131
+ expect(await boot(true)(req, {}, policy)).toEqual({ status: 401, message: 'Unauthenticated' })
1132
+ expect(await boot(false)(req, {}, policy)).toBeNull()
1133
+ })
1134
+
1135
+ it('a stamped session whose check cannot be loaded is no session (fail closed)', async () => {
1136
+ const module = { exports: {} as any }
1137
+ // eslint-disable-next-line no-new-func
1138
+ new Function(
1139
+ 'require',
1140
+ 'module',
1141
+ 'exports',
1142
+ 'process',
1143
+ 'Buffer',
1144
+ generateWorkflowAuthHelperFile({})
1145
+ )(
1146
+ (id: string) => {
1147
+ if (id === 'next-auth/jwt') {
1148
+ return { getToken: async () => stamped }
1149
+ }
1150
+ if (id === './server-runtime') {
1151
+ return { isInternalCall: () => false }
1152
+ }
1153
+ if (id === 'crypto') {
1154
+ return nodeCrypto
1155
+ }
1156
+ throw new Error("Cannot find module '" + id + "'")
1157
+ },
1158
+ module,
1159
+ module.exports,
1160
+ { env: { NEXTAUTH_SECRET: 's' } },
1161
+ Buffer
1162
+ )
1163
+ const req = { headers: { host: 'shop.example', cookie: '__Host-next-auth.session-token=mine' } }
1164
+ expect(
1165
+ await module.exports.guardWorkflowRequest(req, {}, { requiresAuth: true, allowedRoles: [] })
1166
+ ).toEqual({ status: 401, message: 'Unauthenticated' })
1167
+ })
1168
+
1169
+ it('every server file reads the session through the one reader, from its own folder', () => {
1170
+ // [file the reader is inlined into, emitted code]: the revocation module
1171
+ // each one requires must be utils/auth/session-revocation.js from that folder.
1172
+ const posix = require('path').posix
1173
+ const consumers: Array<[string, string]> = [
1174
+ ['pages/api/account/delete-current.js', generateAccountDeleteRoute({})],
1175
+ ['utils/auth/page-access.js', generatePageAccessGuardModule(AUTH)],
1176
+ ['utils/workflows/workflow-auth.js', generateWorkflowAuthHelperFile({})],
1177
+ ]
1178
+ for (const [file, code] of consumers) {
1179
+ const specifier = /require\('([^']*utils\/auth\/session-revocation)'\)/.exec(code)
1180
+ expect(specifier).not.toBeNull()
1181
+ expect(posix.join(posix.dirname(file), specifier![1])).toBe('utils/auth/session-revocation')
1182
+ expect(code).toContain('revocation.liveSessionToken(token)')
1183
+ }
1184
+ })
1185
+ })
1186
+
1187
+ describe('client nodes and project wiring', () => {
1188
+ it('the client nodes send only the address, or the token and password, to their routes', async () => {
1189
+ const calls: Array<{ url: string; body: Row }> = []
1190
+ const fakeFetch = async (url: string, init: { body: string }) => {
1191
+ calls.push({ url, body: JSON.parse(init.body) })
1192
+ return { ok: true, json: async () => ({ success: true, valid: true, changed: true }) }
1193
+ }
1194
+ const run = (source: string) =>
1195
+ // eslint-disable-next-line no-new-func
1196
+ new Function('fetch', `${source}\nreturn ${source.match(/function (\w+)/)![1]};`)(fakeFetch)
1197
+ await run(accountRequestPasswordReset.generateHandler())(
1198
+ { email: ' v@site.test ', apiKey: 'K', body: '<p/>' },
1199
+ {}
1200
+ )
1201
+ expect(
1202
+ await run(accountResetPassword.generateHandler())({ token: 't1', password: 'p' }, {})
1203
+ ).toEqual({
1204
+ valid: true,
1205
+ changed: true,
1206
+ })
1207
+ await run(accountResetPassword.generateHandler())({ token: 't2', checkOnly: true }, {})
1208
+ expect(calls).toEqual([
1209
+ { url: '/api/auth/forgot-password', body: { email: 'v@site.test' } },
1210
+ { url: '/api/auth/reset-password', body: { token: 't1', password: 'p' } },
1211
+ { url: '/api/auth/reset-password', body: { token: 't2', checkOnly: true } },
1212
+ ])
1213
+ })
1214
+
1215
+ it('emits the routes, the revocation check and an empty AUTH_PEPPER_SECRET for password sign-in', async () => {
1216
+ const files = new Map<string, any>()
1217
+ const structure = {
1218
+ uidl: {
1219
+ name: 'Shop',
1220
+ globals: { env: {} as Record<string, string> },
1221
+ authentication: AUTH,
1222
+ dataSources: {},
1223
+ workflows: {
1224
+ workflows: {
1225
+ wf: {
1226
+ id: 'wf',
1227
+ name: 'Forgot Password Flow',
1228
+ trigger: { type: 'event-element-clicked', nodeId: 't', config: {} },
1229
+ nodes: [
1230
+ {
1231
+ id: 'n1',
1232
+ type: 'account-request-password-reset',
1233
+ config: {
1234
+ email: 'x',
1235
+ emailProvider: 'email-resend',
1236
+ from: 'shop@brand.test',
1237
+ apiKey: {
1238
+ type: 'dynamic',
1239
+ content: { referenceType: 'secret', id: 'RESEND_KEY' },
1240
+ },
1241
+ subject: 'Reset',
1242
+ body: '<a href="{{resetUrl}}">Reset</a>',
1243
+ resetPagePath: '/account/new-password',
1244
+ },
1245
+ },
1246
+ ],
1247
+ edges: [],
1248
+ },
1249
+ },
1250
+ },
1251
+ },
1252
+ files,
1253
+ dependencies: {} as Record<string, string>,
1254
+ devDependencies: {},
1255
+ strategy: {},
1256
+ }
1257
+ await new NextWorkflowProjectPlugin().runAfter(structure as never)
1258
+ const content = (key: string) => files.get(key)?.files?.[0]?.content as string
1259
+ expect(content('auth-forgot-password-route')).toContain(
1260
+ 'var RESET_PAGE_PATH = "/account/new-password";'
1261
+ )
1262
+ expect(content('auth-forgot-password-route')).toContain(
1263
+ 'var EMAIL_SECRET_ENV_NAME = "RESEND_KEY";'
1264
+ )
1265
+ expect(content('auth-reset-password-route')).toContain('consumePasswordReset')
1266
+ expect(content('auth-session-revocation')).toContain('liveSessionToken')
1267
+ expect(content('auth-nextauth-route')).toContain('throttleCredentialsSignIn')
1268
+ expect(structure.uidl.globals.env.AUTH_PEPPER_SECRET).toBe('')
1269
+ expect(structure.dependencies.resend).toBeTruthy()
1270
+ })
1271
+
1272
+ it('leaves a project without password sign-in as it was', () => {
1273
+ const oauthOnly = { ...AUTH, passwordAuthEnabled: false } as UIDLAuthentication
1274
+ const optionsSource = generateAuthOptionsFile(oauthOnly, null)
1275
+ for (const marker of [
1276
+ 'session-revocation',
1277
+ 'checkSignInAllowed',
1278
+ 'oauthEmailIsVerified',
1279
+ 'revoked',
1280
+ ]) {
1281
+ expect(optionsSource).not.toContain(marker)
1282
+ }
1283
+ expect(generateNextAuthRouteFile()).not.toContain('security-runtime')
1284
+ expect(generateNextAuthRouteFile()).toBe(
1285
+ generateNextAuthRouteFile({ passwordAuthEnabled: false })
1286
+ )
1287
+ })
1288
+ })
1289
+
1290
+ describe('sign-up never tells whether an address already has an account', () => {
1291
+ it('answers a taken address exactly like a new one, and tells its owner by email', async () => {
1292
+ const db = new FakePostgres()
1293
+ db.users.push({ ...VICTIM, password: 'x' })
1294
+ const app = loadApp({ db, welcome: true })
1295
+ const signup = app.load('signup')
1296
+
1297
+ const taken = fakeResponse()
1298
+ await signup(request({ email: VICTIM.email, password: 'Passw0rd!', name: 'Eve' }), taken)
1299
+ const fresh = fakeResponse()
1300
+ await signup(request({ email: 'new@site.test', password: 'Passw0rd!', name: 'Eve' }), fresh)
1301
+
1302
+ expect(taken.statusCode).toBe(201)
1303
+ expect(fresh.statusCode).toBe(201)
1304
+ expect(taken.body).toEqual({ user: { email: VICTIM.email, name: 'Eve' } })
1305
+ expect(fresh.body).toEqual({ user: { email: 'new@site.test', name: 'Eve' } })
1306
+ expect(Object.keys(taken.body.user)).toEqual(Object.keys(fresh.body.user))
1307
+ // Nothing about the existing account (its id, role) leaves the server.
1308
+ expect(JSON.stringify(taken.body)).not.toContain(VICTIM.id)
1309
+ expect(app.db.users).toHaveLength(2)
1310
+
1311
+ const mails = app.sent.filter((entry) => entry.to)
1312
+ expect(mails.map((mail) => mail.to)).toEqual([[VICTIM.email], ['new@site.test']])
1313
+ expect(mails[0].subject).toBe('You already have an account')
1314
+ expect(mails[0].html).toContain('Forgot password')
1315
+ expect(mails[1].subject).toBe('Welcome')
1316
+ })
1317
+
1318
+ it('sends one address at most 3 "you already have an account" emails an hour', async () => {
1319
+ const db = new FakePostgres()
1320
+ db.users.push({ ...VICTIM, password: 'x' })
1321
+ const app = loadApp({ db, welcome: true })
1322
+ for (let i = 0; i < 5; i++) {
1323
+ const res = fakeResponse()
1324
+ await app.load('signup')(
1325
+ request({ email: VICTIM.email, password: 'Passw0rd!' }, '198.51.100.' + (i + 30)),
1326
+ res
1327
+ )
1328
+ expect(res.statusCode).toBe(201)
1329
+ }
1330
+ expect(app.sent.filter((entry) => entry.to)).toHaveLength(3)
1331
+ })
1332
+ })
1333
+
1334
+ describe('session cookies cannot be tossed by a sibling site', () => {
1335
+ it('names the session and callback-url cookies __Host- on https, and keeps the defaults on http', () => {
1336
+ const https = loadApp().load('options')
1337
+ expect(https.cookies).toEqual({
1338
+ sessionToken: {
1339
+ name: '__Host-next-auth.session-token',
1340
+ options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true },
1341
+ },
1342
+ callbackUrl: {
1343
+ name: '__Host-next-auth.callback-url',
1344
+ options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true },
1345
+ },
1346
+ })
1347
+ expect(JSON.stringify(https.cookies)).not.toContain('domain')
1348
+ const local = loadApp({ env: { ...ENV, NEXTAUTH_URL: 'http://localhost:3000' } }).load(
1349
+ 'options'
1350
+ )
1351
+ expect(local.cookies).toBeUndefined()
1352
+ // OAuth-only projects get it too.
1353
+ const oauthOnly = generateAuthOptionsFile({ ...AUTH, passwordAuthEnabled: false }, null)
1354
+ expect(oauthOnly).toContain("name: '__Host-next-auth.session-token'")
1355
+ })
1356
+
1357
+ const planted = '__Secure-next-auth.session-token=evil; next-auth.session-token=evil'
1358
+ const anySession = async () => ({ id: 'attacker', role: 'admin' })
1359
+
1360
+ it('the page guard ignores a planted cookie and reads only the __Host- one', async () => {
1361
+ const load = () => {
1362
+ const module = { exports: {} as any }
1363
+ // eslint-disable-next-line no-new-func
1364
+ new Function(
1365
+ 'require',
1366
+ 'module',
1367
+ 'exports',
1368
+ 'process',
1369
+ 'console',
1370
+ generatePageAccessGuardModule({
1371
+ ...AUTH,
1372
+ passwordAuthEnabled: false,
1373
+ } as UIDLAuthentication)
1374
+ )(
1375
+ (id: string) => {
1376
+ if (id === 'next-auth/jwt') {
1377
+ return { getToken: anySession }
1378
+ }
1379
+ throw new Error(id)
1380
+ },
1381
+ module,
1382
+ module.exports,
1383
+ { env: { NEXTAUTH_SECRET: 's' } },
1384
+ { error: () => undefined }
1385
+ )
1386
+ return module.exports.guardPageAccess
1387
+ }
1388
+ const context = (cookie: string) => ({
1389
+ req: { headers: { cookie, 'x-forwarded-proto': 'https', host: 'victim.teleporthq.app' } },
1390
+ res: { setHeader: () => undefined },
1391
+ resolvedUrl: '/admin',
1392
+ })
1393
+ const policy = { requiresAuth: true, allowedRoles: ['admin'] }
1394
+ expect((await load()(context(planted), policy)).redirect).toBeDefined()
1395
+ expect(await load()(context('__Host-next-auth.session-token=mine'), policy)).toBeNull()
1396
+ })
1397
+
1398
+ it('the workflow route guard ignores a planted cookie', async () => {
1399
+ const module = { exports: {} as any }
1400
+ // eslint-disable-next-line no-new-func
1401
+ new Function(
1402
+ 'require',
1403
+ 'module',
1404
+ 'exports',
1405
+ 'process',
1406
+ 'Buffer',
1407
+ generateWorkflowAuthHelperFile({})
1408
+ )(
1409
+ (id: string) => {
1410
+ if (id === 'next-auth/jwt') {
1411
+ return { getToken: anySession }
1412
+ }
1413
+ if (id === './server-runtime') {
1414
+ return { isInternalCall: () => false }
1415
+ }
1416
+ if (id === 'crypto') {
1417
+ return nodeCrypto
1418
+ }
1419
+ throw new Error(id)
1420
+ },
1421
+ module,
1422
+ module.exports,
1423
+ { env: { NEXTAUTH_SECRET: 's' } },
1424
+ Buffer
1425
+ )
1426
+ const guard = module.exports.guardWorkflowRequest
1427
+ const req = (cookie: string) => ({
1428
+ headers: { cookie, 'x-forwarded-proto': 'https', host: 'victim.teleporthq.app' },
1429
+ })
1430
+ const policy = { requiresAuth: true, allowedRoles: [] }
1431
+ expect(await guard(req(planted), {}, policy)).toEqual({
1432
+ status: 401,
1433
+ message: 'Unauthenticated',
1434
+ })
1435
+ expect(await guard(req('__Host-next-auth.session-token=mine'), {}, policy)).toBeNull()
1436
+ })
1437
+
1438
+ it('the middleware ignores a planted cookie (no decode, no session round trip)', async () => {
1439
+ const code = generateMiddlewareFile({
1440
+ ...AUTH,
1441
+ authPages: { signIn: { pageId: 's', pageName: 's', route: '/sign-in' } },
1442
+ pageProtection: {
1443
+ p: { requiresAuth: true, allowedRoles: [], pageName: 'A', route: '/account' },
1444
+ },
1445
+ } as never)
1446
+ .replace("import { NextResponse } from 'next/server';", '')
1447
+ .replace("import { getToken } from 'next-auth/jwt';", '')
1448
+ .replace('export default middleware;', '')
1449
+ .replace('export const config = {', 'const config = {')
1450
+ const fetched: string[] = []
1451
+ const middleware = new Function(
1452
+ 'NextResponse',
1453
+ 'getToken',
1454
+ 'fetch',
1455
+ 'process',
1456
+ `${code}; return middleware;`
1457
+ )(
1458
+ {
1459
+ next: () => ({ kind: 'next' }),
1460
+ redirect: (url: URL) => ({ kind: 'redirect', location: String(url) }),
1461
+ },
1462
+ anySession,
1463
+ async (url: string) => {
1464
+ fetched.push(url)
1465
+ return { ok: true, json: async () => ({ user: { id: 'attacker' } }) }
1466
+ },
1467
+ { env: { NEXTAUTH_SECRET: 's' } }
1468
+ )
1469
+ const edgeRequest = (cookie: string) => ({
1470
+ nextUrl: { pathname: '/account' },
1471
+ url: 'https://victim.teleporthq.app/account',
1472
+ cookies: { get: () => undefined },
1473
+ headers: { get: (name: string) => (name === 'cookie' ? cookie : '') },
1474
+ })
1475
+ expect((await middleware(edgeRequest(planted))).kind).toBe('redirect')
1476
+ expect(fetched).toEqual([])
1477
+ expect((await middleware(edgeRequest('__Host-next-auth.session-token=mine'))).kind).toBe('next')
1478
+ })
1479
+ })
1480
+
1481
+ describe('middleware matcher (Task 3b finding)', () => {
1482
+ it('leaves out only the API routes, never a page whose path starts with "api"', () => {
1483
+ const middleware = generateMiddlewareFile({
1484
+ ...AUTH,
1485
+ pageProtection: {
1486
+ p1: { requiresAuth: true, allowedRoles: ['admin'], pageName: 'Keys', route: '/api-keys' },
1487
+ },
1488
+ } as never)
1489
+ const matcher = /matcher: \['\/', '\/\(([^']*)\)'\]/.exec(middleware)
1490
+ expect(matcher).not.toBeNull()
1491
+ const pattern = new RegExp('^/(' + matcher![1].replace(/\\\\/g, '\\') + ')$')
1492
+ for (const page of ['/api-keys', '/apiary', '/about', '/api-keys/new']) {
1493
+ expect(pattern.test(page)).toBe(true)
1494
+ }
1495
+ for (const route of [
1496
+ '/api',
1497
+ '/api/',
1498
+ '/api/auth/session',
1499
+ '/_next/static/x.js',
1500
+ '/favicon.ico',
1501
+ ]) {
1502
+ expect(pattern.test(route)).toBe(false)
1503
+ }
1504
+ })
1505
+ })
1506
+
1507
+ describe('peekRateLimitRequest (security runtime)', () => {
1508
+ const runtime = () => {
1509
+ const module = { exports: {} as any }
1510
+ // eslint-disable-next-line no-new-func
1511
+ new Function(
1512
+ 'module',
1513
+ 'exports',
1514
+ 'require',
1515
+ 'process',
1516
+ 'console',
1517
+ 'Buffer',
1518
+ generateSecurityRuntimeCode()
1519
+ )(
1520
+ module,
1521
+ module.exports,
1522
+ (id: string) =>
1523
+ id === 'crypto'
1524
+ ? nodeCrypto
1525
+ : id === 'net'
1526
+ ? net
1527
+ : (() => {
1528
+ throw new Error(id)
1529
+ })(),
1530
+ { env: {} },
1531
+ { warn: () => undefined, error: () => undefined, log: () => undefined },
1532
+ Buffer
1533
+ )
1534
+ return module.exports
1535
+ }
1536
+
1537
+ it('reads what rateLimitRequest counted without counting itself', async () => {
1538
+ const security = runtime()
1539
+ const req = { headers: {}, socket: { remoteAddress: '203.0.113.9' } }
1540
+ const options = {
1541
+ scope: 'auth-signin-failures',
1542
+ limit: 2,
1543
+ windowSeconds: 900,
1544
+ keyParts: ['a@b.c'],
1545
+ }
1546
+ for (let i = 0; i < 5; i++) {
1547
+ expect((await security.peekRateLimitRequest(req, options)).allowed).toBe(true)
1548
+ }
1549
+ await security.rateLimitRequest(req, null, options)
1550
+ expect((await security.peekRateLimitRequest(req, options)).allowed).toBe(true)
1551
+ await security.rateLimitRequest(req, null, options)
1552
+ const refused = await security.peekRateLimitRequest(req, options)
1553
+ expect(refused.allowed).toBe(false)
1554
+ expect(refused.retryAfter).toBeGreaterThan(0)
1555
+ // Another email, or another visitor, has its own count.
1556
+ expect(
1557
+ (await security.peekRateLimitRequest(req, { ...options, keyParts: ['x@b.c'] })).allowed
1558
+ ).toBe(true)
1559
+ expect(
1560
+ (
1561
+ await security.peekRateLimitRequest(
1562
+ { headers: {}, socket: { remoteAddress: '203.0.113.10' } },
1563
+ options
1564
+ )
1565
+ ).allowed
1566
+ ).toBe(true)
1567
+ })
1568
+
1569
+ it('refuses arguments that say nothing about what it limits', async () => {
1570
+ const security = runtime()
1571
+ await expect(
1572
+ security.peekRateLimitRequest({}, { scope: '', limit: 1, windowSeconds: 1 })
1573
+ ).rejects.toThrow(TypeError)
1574
+ await expect(
1575
+ security.peekRateLimitRequest({}, { scope: 's', limit: 0, windowSeconds: 1 })
1576
+ ).rejects.toThrow(TypeError)
1577
+ })
1578
+ })
1579
+
1580
+ describe('review fix round 1: the session follows its account, by id', () => {
1581
+ const victimDb = () => {
1582
+ const db = new FakePostgres()
1583
+ db.users.push({ ...VICTIM, password: pepperedHash('Right#Pass1', ENV.AUTH_PEPPER_SECRET) })
1584
+ return db
1585
+ }
1586
+
1587
+ // A password sign-in on one instance: its stamped token.
1588
+ const signedIn = async (db: FakePostgres) => {
1589
+ const options = loadApp({ db }).load('options')
1590
+ const user = await options.providers[0].authorize(
1591
+ { email: VICTIM.email, password: 'Right#Pass1' },
1592
+ { headers: {} }
1593
+ )
1594
+ const token: Row = { email: VICTIM.email, sub: VICTIM.id }
1595
+ await options.callbacks.jwt({ token, user })
1596
+ expect(typeof token.__sessionStamp).toBe('string')
1597
+ return { options, token }
1598
+ }
1599
+
1600
+ const signedInRequest = {
1601
+ method: 'POST',
1602
+ headers: { host: 'shop.example', cookie: '__Host-next-auth.session-token=mine' },
1603
+ socket: { remoteAddress: '203.0.113.7' },
1604
+ }
1605
+
1606
+ // `__tqSessionToken` of a route in pages/api/<folder>/, over one instance's
1607
+ // utils/auth/session-revocation.js; `decoded` is what the cookie decodes to.
1608
+ const sessionReader = (app: ReturnType<typeof loadApp>, decoded: Row) => {
1609
+ const module = { exports: {} as any }
1610
+ // eslint-disable-next-line no-new-func
1611
+ new Function(
1612
+ 'require',
1613
+ 'module',
1614
+ 'exports',
1615
+ 'process',
1616
+ generateCommonJsSessionTokenResolverCode('../../..') + '\nmodule.exports = __tqSessionToken;'
1617
+ )(
1618
+ (id: string) => {
1619
+ if (id === 'next-auth/jwt') {
1620
+ return { getToken: async () => ({ ...decoded }) }
1621
+ }
1622
+ if (id === '../../../utils/auth/session-revocation') {
1623
+ return app.load('revocation')
1624
+ }
1625
+ throw new Error('unexpected require: ' + id)
1626
+ },
1627
+ module,
1628
+ module.exports,
1629
+ { env: app.env }
1630
+ )
1631
+ return module.exports as (req: Row) => Promise<Row | null>
1632
+ }
1633
+
1634
+ it('A1: a stamped session whose account cannot be read is no session, and account deletion answers 401', async () => {
1635
+ const db = victimDb()
1636
+ const { options, token } = await signedIn(db)
1637
+ db.failLookups = true
1638
+ // Another instance: nothing cached.
1639
+ const app = loadApp({ db })
1640
+ expect(await sessionReader(app, token)(signedInRequest)).toBeNull()
1641
+
1642
+ const statements: string[] = []
1643
+ const module = { exports: {} as any }
1644
+ // eslint-disable-next-line no-new-func
1645
+ new Function(
1646
+ 'require',
1647
+ 'module',
1648
+ 'exports',
1649
+ 'process',
1650
+ 'console',
1651
+ generateAccountDeleteRoute({})
1652
+ )(
1653
+ (id: string) => {
1654
+ if (id === 'pg') {
1655
+ return {
1656
+ Client: class {
1657
+ public async connect() {
1658
+ return undefined
1659
+ }
1660
+ public async end() {
1661
+ return undefined
1662
+ }
1663
+ public async query(sql: string) {
1664
+ statements.push(sql)
1665
+ return { rows: [] }
1666
+ }
1667
+ },
1668
+ }
1669
+ }
1670
+ if (id === 'next-auth/jwt') {
1671
+ return { getToken: async () => ({ ...token }) }
1672
+ }
1673
+ if (id === '../../../utils/auth/session-revocation') {
1674
+ return app.load('revocation')
1675
+ }
1676
+ if (id === '../../../utils/email/email-locale') {
1677
+ return { resolveRequestLocale: () => '' }
1678
+ }
1679
+ throw new Error('unexpected require: ' + id)
1680
+ },
1681
+ module,
1682
+ module.exports,
1683
+ { env: { ...app.env, TELEPORT_DB_CONNECTION_STRING: 'postgres://x' } },
1684
+ { error: () => undefined, warn: () => undefined, log: () => undefined }
1685
+ )
1686
+ const deleted = fakeResponse()
1687
+ await module.exports(signedInRequest, deleted)
1688
+ expect(deleted.statusCode).toBe(401)
1689
+ expect(statements.filter((sql) => sql.startsWith('DELETE'))).toEqual([])
1690
+
1691
+ // /api/auth/session alone keeps the token through the hiccup.
1692
+ await expect(options.callbacks.jwt({ token, trigger: 'update' })).resolves.toBe(token)
1693
+ // No verdict was cached: once the store answers, the session counts again.
1694
+ db.failLookups = false
1695
+ expect(await sessionReader(app, token)(signedInRequest)).toMatchObject({ id: VICTIM.id })
1696
+ })
1697
+
1698
+ it('A4: a deleted account ends its sessions', async () => {
1699
+ const db = victimDb()
1700
+ const { options, token } = await signedIn(db)
1701
+ db.users = []
1702
+ const app = loadApp({ db })
1703
+ expect(await app.load('revocation').liveSessionToken(token)).toBeNull()
1704
+ expect(await sessionReader(app, token)(signedInRequest)).toBeNull()
1705
+ await expect(options.callbacks.jwt({ token, trigger: 'update' })).rejects.toThrow(
1706
+ 'SessionRevoked'
1707
+ )
1708
+ })
1709
+
1710
+ it('A4: after an email change, a later password change still ends the earlier sessions', async () => {
1711
+ const db = victimDb()
1712
+ const { options, token } = await signedIn(db)
1713
+ db.users[0].email = 'renamed@site.test'
1714
+ await options.callbacks.jwt({ token, trigger: 'update' })
1715
+ expect(token).toMatchObject({ id: VICTIM.id, email: 'renamed@site.test' })
1716
+ expect(await sessionReader(loadApp({ db }), token)(signedInRequest)).toMatchObject({
1717
+ id: VICTIM.id,
1718
+ email: 'renamed@site.test',
1719
+ })
1720
+
1721
+ db.users[0].password = pepperedHash('Other#Pass2', ENV.AUTH_PEPPER_SECRET)
1722
+ expect(await sessionReader(loadApp({ db }), token)(signedInRequest)).toBeNull()
1723
+ await expect(options.callbacks.jwt({ token, trigger: 'update' })).rejects.toThrow(
1724
+ 'SessionRevoked'
1725
+ )
1726
+ })
1727
+
1728
+ it('A4: a lowered role counts on the next request: the reader hands out the stored role, and the data API stops the staff bypass', async () => {
1729
+ const db = victimDb()
1730
+ const { token } = await signedIn(db)
1731
+ expect(token.role).toBe('admin')
1732
+
1733
+ // A server segment acting for the signed-in visitor writes a role onto an account.
1734
+ const roleWrite = async (app: ReturnType<typeof loadApp>) => {
1735
+ const module = { exports: {} as any }
1736
+ // eslint-disable-next-line no-new-func
1737
+ new Function(
1738
+ 'require',
1739
+ 'module',
1740
+ 'exports',
1741
+ 'process',
1742
+ 'console',
1743
+ generateDataAPIRoute({ authUsersTableName: 'users' })
1744
+ )(
1745
+ (id: string) => {
1746
+ if (id === 'pg') {
1747
+ return {
1748
+ Client: class {
1749
+ public async connect() {
1750
+ return undefined
1751
+ }
1752
+ public async end() {
1753
+ return undefined
1754
+ }
1755
+ public async query() {
1756
+ return { rows: [], rowCount: 0 }
1757
+ }
1758
+ },
1759
+ }
1760
+ }
1761
+ if (id === 'next-auth/jwt') {
1762
+ return { getToken: async () => ({ ...token }) }
1763
+ }
1764
+ if (id === '../../../utils/auth/session-revocation') {
1765
+ return app.load('revocation')
1766
+ }
1767
+ if (/(^|\/)utils\/security-runtime$/.test(id)) {
1768
+ return app.load('runtime')
1769
+ }
1770
+ throw new Error('unexpected require: ' + id)
1771
+ },
1772
+ module,
1773
+ module.exports,
1774
+ { env: { ...app.env, TELEPORT_DB_CONNECTION_STRING: 'postgres://x' } },
1775
+ { error: () => undefined, warn: () => undefined, log: () => undefined }
1776
+ )
1777
+ const answer = fakeResponse()
1778
+ await module.exports(
1779
+ {
1780
+ method: 'POST',
1781
+ query: { params: ['ds-1', 'update'] },
1782
+ headers: {
1783
+ ...signedInRequest.headers,
1784
+ 'x-internal-data-secret': app.env.NEXTAUTH_SECRET,
1785
+ },
1786
+ body: {
1787
+ tableName: 'users',
1788
+ columnMappings: { role: 'admin' },
1789
+ filters: [{ source: 'id', operand: '=', destination: 'u-other' }],
1790
+ },
1791
+ },
1792
+ answer
1793
+ )
1794
+ return answer
1795
+ }
1796
+
1797
+ expect((await sessionReader(loadApp({ db }), token)(signedInRequest))!.role).toBe('admin')
1798
+ expect((await roleWrite(loadApp({ db }))).statusCode).not.toBe(403)
1799
+
1800
+ db.users[0].role = 'user'
1801
+ // The token still says admin; the stored row does not.
1802
+ expect(token.role).toBe('admin')
1803
+ expect((await sessionReader(loadApp({ db }), token)(signedInRequest))!.role).toBe('user')
1804
+ const refused = await roleWrite(loadApp({ db }))
1805
+ expect(refused.statusCode).toBe(403)
1806
+ })
1807
+ })
1808
+
1809
+ describe('review fix round 1: OAuth identities', () => {
1810
+ it('A2: an OAuth-only project keeps an unverified Microsoft email out of every local account', async () => {
1811
+ const auth = {
1812
+ ...AUTH,
1813
+ passwordAuthEnabled: false,
1814
+ providers: [
1815
+ {
1816
+ id: 'azure-ad',
1817
+ name: 'Azure AD',
1818
+ credentials: { AUTH_AZURE_AD_ID: '', AUTH_AZURE_AD_SECRET: '' },
1819
+ },
1820
+ ],
1821
+ } as UIDLAuthentication
1822
+ const db = new FakePostgres()
1823
+ db.users.push({ ...VICTIM, password: 'x' })
1824
+ const options = loadApp({ auth, db }).load('options')
1825
+ const token: Row = { email: VICTIM.email, sub: 'ms-1' }
1826
+ await options.callbacks.jwt({
1827
+ token,
1828
+ user: { id: 'ms-1', email: VICTIM.email },
1829
+ account: { provider: 'azure-ad', type: 'oauth', providerAccountId: 'ms-1' },
1830
+ profile: { email: VICTIM.email, tid: '11111111-2222-3333-4444-555555555555' },
1831
+ })
1832
+ await options.callbacks.jwt({ token, trigger: 'update' })
1833
+ expect(token.email).toBeUndefined()
1834
+ expect(token.role).toBeUndefined()
1835
+ expect(token.id).toBe('oauth:azure-ad:ms-1')
1836
+ })
1837
+
1838
+ it('A2: an OAuth-only project keeps a verified Google sign-in as it was', async () => {
1839
+ const auth = {
1840
+ ...AUTH,
1841
+ passwordAuthEnabled: false,
1842
+ providers: [{ id: 'google', name: 'Google', credentials: {} }],
1843
+ } as UIDLAuthentication
1844
+ const options = loadApp({ auth }).load('options')
1845
+ const token: Row = { email: VICTIM.email, sub: 'g-1' }
1846
+ await options.callbacks.jwt({
1847
+ token,
1848
+ user: { id: 'g-1', email: VICTIM.email },
1849
+ account: { provider: 'google', type: 'oauth', providerAccountId: 'g-1' },
1850
+ profile: { email: VICTIM.email, email_verified: true },
1851
+ })
1852
+ expect(token.email).toBe(VICTIM.email)
1853
+ expect(token.id).toBe('g-1')
1854
+ })
1855
+
1856
+ it('A7: a verified GitHub sign-in with no local account gets a namespaced id; with one, the local id', async () => {
1857
+ const auth = {
1858
+ ...AUTH,
1859
+ providers: [{ id: 'github', name: 'GitHub', credentials: {} }],
1860
+ } as UIDLAuthentication
1861
+ const signIn = async (withAccount: boolean) => {
1862
+ const db = new FakePostgres()
1863
+ if (withAccount) {
1864
+ db.users.push({ ...VICTIM, password: 'x' })
1865
+ }
1866
+ const options = loadApp({
1867
+ auth,
1868
+ db,
1869
+ fetch: async () => ({
1870
+ ok: true,
1871
+ json: async () => [{ email: VICTIM.email, verified: true, primary: true }],
1872
+ }),
1873
+ }).load('options')
1874
+ const token: Row = { email: VICTIM.email, sub: 'gh-1' }
1875
+ await options.callbacks.jwt({
1876
+ token,
1877
+ user: { id: 'gh-1', email: VICTIM.email },
1878
+ account: {
1879
+ provider: 'github',
1880
+ type: 'oauth',
1881
+ providerAccountId: 'gh-1',
1882
+ access_token: 't',
1883
+ },
1884
+ profile: { email: VICTIM.email },
1885
+ })
1886
+ await options.callbacks.jwt({ token, trigger: 'update' })
1887
+ return token
1888
+ }
1889
+ const without = await signIn(false)
1890
+ expect(without.id).toBe('oauth:github:gh-1')
1891
+ expect(without.role).toBeUndefined()
1892
+ expect(await signIn(true)).toMatchObject({ id: VICTIM.id, role: 'admin', email: VICTIM.email })
1893
+ })
1894
+ })
1895
+
1896
+ describe('review fix round 1: reset links', () => {
1897
+ // Postgres's row lock, for the reset's one statement: an UPDATE of a row that
1898
+ // another open statement is updating waits for it, then re-checks its WHERE
1899
+ // against the row as that one left it (READ COMMITTED). The first statement
1900
+ // holds the row a moment, so the second arrives while it is locked.
1901
+ class RowLockingPostgres extends FakePostgres {
1902
+ public waited = 0
1903
+ private held = new Map<string, Promise<void>>()
1904
+
1905
+ public query(sql: string, values: any[] = []): any {
1906
+ if (!sql.startsWith('WITH consumed AS (')) {
1907
+ return super.query(sql, values)
1908
+ }
1909
+ return this.locked(String(values[0]), () => super.query(sql, values))
1910
+ }
1911
+
1912
+ private async locked(key: string, run: () => unknown) {
1913
+ while (this.held.has(key)) {
1914
+ this.waited++
1915
+ await this.held.get(key)
1916
+ }
1917
+ let release: () => void = () => undefined
1918
+ this.held.set(
1919
+ key,
1920
+ new Promise<void>((resolve) => {
1921
+ release = resolve
1922
+ })
1923
+ )
1924
+ try {
1925
+ await new Promise((resolve) => setTimeout(resolve, 20))
1926
+ return run()
1927
+ } finally {
1928
+ this.held.delete(key)
1929
+ release()
1930
+ }
1931
+ }
1932
+ }
1933
+
1934
+ it('A9: two simultaneous resets with one link set the password once', async () => {
1935
+ const db = new RowLockingPostgres()
1936
+ db.users.push({ ...VICTIM, password: pepperedHash('Original#1', ENV.AUTH_PEPPER_SECRET) })
1937
+ const app = loadApp({ db })
1938
+ await app.load('forgot')(request({ email: VICTIM.email }), fakeResponse())
1939
+ const link = tokenFromEmail(app.sent)
1940
+ const reset = app.load('reset')
1941
+ const first = fakeResponse()
1942
+ const second = fakeResponse()
1943
+ await Promise.all([
1944
+ reset(request({ token: link, password: 'First#Pass1' }, '203.0.113.21'), first),
1945
+ reset(request({ token: link, password: 'Second#Pass2' }, '203.0.113.22'), second),
1946
+ ])
1947
+ expect(db.waited).toBeGreaterThan(0)
1948
+ expect([first.body, second.body]).toContainEqual({ valid: true, changed: true })
1949
+ expect([first.body, second.body]).toContainEqual({ valid: false, changed: false })
1950
+ const winner = first.body.changed ? 'First#Pass1' : 'Second#Pass2'
1951
+ expect(db.users[0].password).toBe(pepperedHash(winner, ENV.AUTH_PEPPER_SECRET))
1952
+ })
1953
+
1954
+ it('m2: the reset email carries the token in the fragment, and that link resets the password', async () => {
1955
+ const db = new FakePostgres()
1956
+ db.users.push({ ...VICTIM, password: pepperedHash('Original#1', ENV.AUTH_PEPPER_SECRET) })
1957
+ const app = loadApp({ db, tokenInFragment: true })
1958
+ await app.load('forgot')(request({ email: VICTIM.email }), fakeResponse())
1959
+ const mail = app.sent.find((entry) => entry.to)
1960
+ expect(String(mail!.html)).toMatch(
1961
+ /https:\/\/shop\.example\/reset-password#token=[0-9a-f]{64}"/
1962
+ )
1963
+ const answer = fakeResponse()
1964
+ await app.load('reset')(
1965
+ request({ token: tokenFromEmail(app.sent), password: 'Brand#New2' }),
1966
+ answer
1967
+ )
1968
+ expect(answer.body).toEqual({ valid: true, changed: true })
1969
+ // Without the option (a reset page that still reads the query), as before.
1970
+ expect(generateForgotPasswordRoute({})).toContain('var TOKEN_LINK_PREFIX = "?token=";')
1971
+ })
1972
+
1973
+ it('m2: the project links with the fragment only when its reset page reads the kept token', async () => {
1974
+ const forgotRoute = async (readerCode: string) => {
1975
+ const files = new Map<string, any>()
1976
+ const structure = {
1977
+ uidl: {
1978
+ name: 'Shop',
1979
+ globals: { env: {} as Record<string, string> },
1980
+ authentication: AUTH,
1981
+ dataSources: {},
1982
+ workflows: {
1983
+ workflows: {
1984
+ forgot: {
1985
+ id: 'forgot',
1986
+ name: 'Forgot Password Flow',
1987
+ trigger: { type: 'event-element-clicked', nodeId: 't', config: {} },
1988
+ nodes: [
1989
+ {
1990
+ id: 'n1',
1991
+ type: 'account-request-password-reset',
1992
+ config: { email: 'x', emailProvider: 'email-resend' },
1993
+ },
1994
+ ],
1995
+ edges: [],
1996
+ },
1997
+ resetPage: {
1998
+ id: 'resetPage',
1999
+ name: 'Reset Password Page Load',
2000
+ trigger: { type: 'event-page-loaded', nodeId: 't', config: {} },
2001
+ nodes: [{ id: 'n2', type: 'general-custom-js', config: { code: readerCode } }],
2002
+ edges: [],
2003
+ },
2004
+ },
2005
+ },
2006
+ },
2007
+ files,
2008
+ dependencies: {} as Record<string, string>,
2009
+ devDependencies: {},
2010
+ strategy: {},
2011
+ }
2012
+ await new NextWorkflowProjectPlugin().runAfter(structure as never)
2013
+ return files.get('auth-forgot-password-route')?.files?.[0]?.content as string
2014
+ }
2015
+ expect(await forgotRoute('sessionStorage.getItem("tq-reset-token")')).toContain(
2016
+ 'var TOKEN_LINK_PREFIX = "#token=";'
2017
+ )
2018
+ expect(await forgotRoute('new URLSearchParams(location.search).get("token")')).toContain(
2019
+ 'var TOKEN_LINK_PREFIX = "?token=";'
2020
+ )
2021
+ })
2022
+ })
2023
+
2024
+ describe('review fix round 2 (A10): an OAuth id is namespaced only where it could collide', () => {
2025
+ const GITHUB = { id: 'github', name: 'GitHub', credentials: {} }
2026
+ const AZURE = {
2027
+ id: 'azure-ad',
2028
+ name: 'Azure AD',
2029
+ credentials: { AUTH_AZURE_AD_ID: '', AUTH_AZURE_AD_SECRET: '' },
2030
+ }
2031
+ const usersTable = (idType: string) => ({
2032
+ users: [
2033
+ { name: 'id', type: idType, nullable: false, isPrimaryKey: true },
2034
+ { name: 'email', type: 'VARCHAR(255)', nullable: true },
2035
+ { name: 'password', type: 'TEXT', nullable: true },
2036
+ ],
2037
+ })
2038
+
2039
+ // A sign-in with no local account (no row), then a session refresh.
2040
+ const signIn = async (
2041
+ auth: UIDLAuthentication,
2042
+ provider: 'github' | 'azure-ad',
2043
+ providerAccountId: string
2044
+ ) => {
2045
+ const options = loadApp({
2046
+ auth,
2047
+ fetch: async () => ({
2048
+ ok: true,
2049
+ json: async () => [{ email: VICTIM.email, verified: true, primary: true }],
2050
+ }),
2051
+ }).load('options')
2052
+ const token: Row = { email: VICTIM.email, sub: providerAccountId }
2053
+ await options.callbacks.jwt({
2054
+ token,
2055
+ user: { id: providerAccountId, email: VICTIM.email },
2056
+ account: { provider, type: 'oauth', providerAccountId, access_token: 't' },
2057
+ // Azure AD: a multi-tenant token without xms_edov (not vouched for).
2058
+ profile: { email: VICTIM.email, tid: '11111111-2222-3333-4444-555555555555' },
2059
+ })
2060
+ await options.callbacks.jwt({ token, trigger: 'update' })
2061
+ return token
2062
+ }
2063
+
2064
+ it('keeps the raw provider id on a UUID users table with one provider', async () => {
2065
+ const github = await signIn(
2066
+ { ...AUTH, providers: [GITHUB], tables: usersTable('UUID') } as UIDLAuthentication,
2067
+ 'github',
2068
+ '583231'
2069
+ )
2070
+ expect(github.id).toBe('583231')
2071
+ expect(github.role).toBeUndefined()
2072
+
2073
+ const azure = await signIn(
2074
+ {
2075
+ ...AUTH,
2076
+ passwordAuthEnabled: false,
2077
+ providers: [AZURE],
2078
+ tables: usersTable('uuid'),
2079
+ } as UIDLAuthentication,
2080
+ 'azure-ad',
2081
+ 'ms-1'
2082
+ )
2083
+ expect(azure.id).toBe('ms-1')
2084
+ expect(azure.email).toBeUndefined()
2085
+ expect(azure.role).toBeUndefined()
2086
+ })
2087
+
2088
+ it('namespaces it on an integer users table, and with more than one provider', async () => {
2089
+ const integer = { ...AUTH, tables: usersTable('SERIAL') }
2090
+ expect(
2091
+ (await signIn({ ...integer, providers: [GITHUB] } as UIDLAuthentication, 'github', '583231'))
2092
+ .id
2093
+ ).toBe('oauth:github:583231')
2094
+ const azure = await signIn(
2095
+ { ...integer, passwordAuthEnabled: false, providers: [AZURE] } as UIDLAuthentication,
2096
+ 'azure-ad',
2097
+ 'ms-1'
2098
+ )
2099
+ expect(azure.id).toBe('oauth:azure-ad:ms-1')
2100
+ expect(azure.email).toBeUndefined()
2101
+
2102
+ const twoProviders = {
2103
+ ...AUTH,
2104
+ providers: [GITHUB, AZURE],
2105
+ tables: usersTable('UUID'),
2106
+ } as UIDLAuthentication
2107
+ expect((await signIn(twoProviders, 'github', '583231')).id).toBe('oauth:github:583231')
2108
+ expect((await signIn(twoProviders, 'azure-ad', 'ms-1')).id).toBe('oauth:azure-ad:ms-1')
2109
+ })
2110
+
2111
+ it('namespaces it where the users id type is unknown (fail-safe against a collision)', async () => {
2112
+ const undeclared = { ...AUTH, providers: [GITHUB], tables: {} } as UIDLAuthentication
2113
+ expect((await signIn(undeclared, 'github', '583231')).id).toBe('oauth:github:583231')
2114
+ })
2115
+
2116
+ it('still adopts the local account of a verified email, whatever the id type', async () => {
2117
+ const db = new FakePostgres()
2118
+ db.users.push({ ...VICTIM, password: 'x' })
2119
+ const options = loadApp({
2120
+ auth: { ...AUTH, providers: [GITHUB], tables: usersTable('UUID') } as UIDLAuthentication,
2121
+ db,
2122
+ fetch: async () => ({
2123
+ ok: true,
2124
+ json: async () => [{ email: VICTIM.email, verified: true, primary: true }],
2125
+ }),
2126
+ }).load('options')
2127
+ const token: Row = { email: VICTIM.email, sub: '583231' }
2128
+ await options.callbacks.jwt({
2129
+ token,
2130
+ user: { id: '583231', email: VICTIM.email },
2131
+ account: {
2132
+ provider: 'github',
2133
+ type: 'oauth',
2134
+ providerAccountId: '583231',
2135
+ access_token: 't',
2136
+ },
2137
+ profile: { email: VICTIM.email },
2138
+ })
2139
+ await options.callbacks.jwt({ token, trigger: 'update' })
2140
+ expect(token).toMatchObject({ id: VICTIM.id, role: 'admin' })
2141
+ })
2142
+ })
2143
+
2144
+ describe('review fix round 2b (A10): stores whose ids are their own keep the raw provider id', () => {
2145
+ const GITHUB = { id: 'github', name: 'GitHub', credentials: {} }
2146
+ const AZURE = {
2147
+ id: 'azure-ad',
2148
+ name: 'Azure AD',
2149
+ credentials: { AUTH_AZURE_AD_ID: '', AUTH_AZURE_AD_SECRET: '' },
2150
+ }
2151
+ // The stores' drivers, as far as loading auth-options needs them.
2152
+ const DRIVERS: Record<string, unknown> = {
2153
+ mongodb: {
2154
+ MongoClient: class {
2155
+ public async connect() {
2156
+ return this
2157
+ }
2158
+ },
2159
+ ObjectId: { isValid: () => false },
2160
+ },
2161
+ 'firebase-admin': {
2162
+ apps: [],
2163
+ initializeApp: () => undefined,
2164
+ credential: { cert: () => ({}) },
2165
+ firestore: () => ({}),
2166
+ },
2167
+ airtable: class {
2168
+ public base() {
2169
+ return () => ({})
2170
+ }
2171
+ },
2172
+ }
2173
+
2174
+ // A verified GitHub sign-in with no local account: the id it signs in with.
2175
+ const signInId = async (dataSourceType: string, providers: unknown[]) => {
2176
+ const options = loadApp({
2177
+ auth: { ...AUTH, dataSourceType, providers, tables: {} } as unknown as UIDLAuthentication,
2178
+ externals: DRIVERS,
2179
+ fetch: async () => ({
2180
+ ok: true,
2181
+ json: async () => [{ email: VICTIM.email, verified: true, primary: true }],
2182
+ }),
2183
+ }).load('options')
2184
+ const token: Row = { email: VICTIM.email, sub: '583231' }
2185
+ await options.callbacks.jwt({
2186
+ token,
2187
+ user: { id: '583231', email: VICTIM.email },
2188
+ account: {
2189
+ provider: 'github',
2190
+ type: 'oauth',
2191
+ providerAccountId: '583231',
2192
+ access_token: 't',
2193
+ },
2194
+ profile: { email: VICTIM.email },
2195
+ })
2196
+ return token.id
2197
+ }
2198
+
2199
+ it('MongoDB with one provider keeps the raw id', async () => {
2200
+ expect(await signInId('mongodb', [GITHUB])).toBe('583231')
2201
+ })
2202
+
2203
+ it('Airtable with one provider keeps the raw id', async () => {
2204
+ expect(await signInId('airtable', [GITHUB])).toBe('583231')
2205
+ })
2206
+
2207
+ it('Firestore with one provider keeps the raw id', async () => {
2208
+ expect(await signInId('firestore', [GITHUB])).toBe('583231')
2209
+ })
2210
+
2211
+ it('MongoDB with two providers namespaces it', async () => {
2212
+ expect(await signInId('mongodb', [GITHUB, AZURE])).toBe('oauth:github:583231')
2213
+ })
2214
+
2215
+ it('a SQL store with no declared users id type still namespaces it', async () => {
2216
+ expect(await signInId('postgresql', [GITHUB])).toBe('oauth:github:583231')
2217
+ })
2218
+ })
2219
+
2220
+ describe('review fix round 3 (A11): the users id type the editor read from the database decides', () => {
2221
+ const GITHUB = { id: 'github', name: 'GitHub', credentials: {} }
2222
+ // The UIDL as the editor now exports it: the id type the database reported, or none.
2223
+ const withLiveIdType = (idType: string) =>
2224
+ ({
2225
+ ...AUTH,
2226
+ providers: [GITHUB],
2227
+ tables: {
2228
+ users: [
2229
+ { name: 'id', type: idType, nullable: false, isPrimaryKey: true },
2230
+ { name: 'email', type: 'character varying', nullable: true },
2231
+ ],
2232
+ },
2233
+ } as UIDLAuthentication)
2234
+
2235
+ const signInId = async (auth: UIDLAuthentication) => {
2236
+ const options = loadApp({
2237
+ auth,
2238
+ fetch: async () => ({
2239
+ ok: true,
2240
+ json: async () => [{ email: VICTIM.email, verified: true, primary: true }],
2241
+ }),
2242
+ }).load('options')
2243
+ const token: Row = { email: VICTIM.email, sub: '583231' }
2244
+ await options.callbacks.jwt({
2245
+ token,
2246
+ user: { id: '583231', email: VICTIM.email },
2247
+ account: {
2248
+ provider: 'github',
2249
+ type: 'oauth',
2250
+ providerAccountId: '583231',
2251
+ access_token: 't',
2252
+ },
2253
+ profile: { email: VICTIM.email },
2254
+ })
2255
+ await options.callbacks.jwt({ token, trigger: 'update' })
2256
+ return token.id
2257
+ }
2258
+
2259
+ it('an existing integer users table namespaces the provider id', async () => {
2260
+ for (const idType of ['integer', 'bigint', 'int4']) {
2261
+ expect([idType, await signInId(withLiveIdType(idType))]).toEqual([
2262
+ idType,
2263
+ 'oauth:github:583231',
2264
+ ])
2265
+ }
2266
+ })
2267
+
2268
+ it('the UUID table the activation created keeps the raw provider id', async () => {
2269
+ expect(await signInId(withLiveIdType('uuid'))).toBe('583231')
2270
+ })
2271
+
2272
+ it('no recorded type namespaces it', async () => {
2273
+ expect(await signInId(withLiveIdType(''))).toBe('oauth:github:583231')
2274
+ })
2275
+ })