n8n-nodes-base 2.36.2 → 2.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist/cjs/build.tsbuildinfo +1 -1
  2. package/dist/credentials/TypeformOAuth2Api.credentials.d.ts.map +1 -1
  3. package/dist/credentials/TypeformOAuth2Api.credentials.js +32 -2
  4. package/dist/credentials/TypeformOAuth2Api.credentials.js.map +1 -1
  5. package/dist/known/credentials.json +2 -0
  6. package/dist/node-definitions/.nodes-hash +1 -1
  7. package/dist/node-definitions/nodes/n8n-nodes-base/googleAnalytics/v2/resource_report/operation_get.ts +2 -2
  8. package/dist/node-definitions/nodes/n8n-nodes-base/grist/v1.schema.js +6 -4
  9. package/dist/node-definitions/nodes/n8n-nodes-base/grist/v1.ts +28 -4
  10. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftOutlook/v2/resource_event/operation_create.ts +2 -2
  11. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/index.ts +3 -1
  12. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/index.schema.js +24 -0
  13. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/index.ts +18 -0
  14. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/index.schema.js +24 -0
  15. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/index.ts +18 -0
  16. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/operation_download.schema.js +26 -0
  17. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/operation_download.ts +49 -0
  18. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/operation_update.schema.js +28 -0
  19. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/operation_update.ts +59 -0
  20. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/operation_upload.schema.js +26 -0
  21. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_file/operation_upload.ts +46 -0
  22. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/index.schema.js +30 -0
  23. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/index.ts +27 -0
  24. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_create.schema.js +25 -0
  25. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_create.ts +47 -0
  26. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_delete.schema.js +25 -0
  27. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_delete.ts +43 -0
  28. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_get.schema.js +26 -0
  29. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_get.ts +48 -0
  30. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_get_all.schema.js +29 -0
  31. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_get_all.ts +68 -0
  32. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_update.schema.js +25 -0
  33. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_update.ts +47 -0
  34. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_upsert.schema.js +25 -0
  35. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_item/operation_upsert.ts +47 -0
  36. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_list/index.schema.js +22 -0
  37. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_list/index.ts +15 -0
  38. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_list/operation_get.schema.js +25 -0
  39. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_list/operation_get.ts +41 -0
  40. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_list/operation_get_all.schema.js +26 -0
  41. package/dist/node-definitions/nodes/n8n-nodes-base/microsoftSharePoint/v2/resource_list/operation_get_all.ts +45 -0
  42. package/dist/nodes/Confluence/actions/attachment/getMany.operation.d.ts +32 -0
  43. package/dist/nodes/Confluence/actions/attachment/getMany.operation.d.ts.map +1 -0
  44. package/dist/nodes/Confluence/actions/attachment/getMany.operation.js +98 -0
  45. package/dist/nodes/Confluence/actions/attachment/getMany.operation.js.map +1 -0
  46. package/dist/nodes/Confluence/actions/attachment/index.d.ts +5 -0
  47. package/dist/nodes/Confluence/actions/attachment/index.d.ts.map +1 -0
  48. package/dist/nodes/Confluence/actions/attachment/index.js +62 -0
  49. package/dist/nodes/Confluence/actions/attachment/index.js.map +1 -0
  50. package/dist/nodes/Confluence/actions/common.d.ts +7 -0
  51. package/dist/nodes/Confluence/actions/common.d.ts.map +1 -1
  52. package/dist/nodes/Confluence/actions/common.js +25 -2
  53. package/dist/nodes/Confluence/actions/common.js.map +1 -1
  54. package/dist/nodes/Confluence/actions/description.d.ts.map +1 -1
  55. package/dist/nodes/Confluence/actions/description.js +6 -0
  56. package/dist/nodes/Confluence/actions/description.js.map +1 -1
  57. package/dist/nodes/Confluence/actions/page/append.operation.d.ts +6 -0
  58. package/dist/nodes/Confluence/actions/page/append.operation.d.ts.map +1 -0
  59. package/dist/nodes/Confluence/actions/page/append.operation.js +85 -0
  60. package/dist/nodes/Confluence/actions/page/append.operation.js.map +1 -0
  61. package/dist/nodes/Confluence/actions/page/bodyEnvelope.d.ts +4 -1
  62. package/dist/nodes/Confluence/actions/page/bodyEnvelope.d.ts.map +1 -1
  63. package/dist/nodes/Confluence/actions/page/bodyEnvelope.js +28 -7
  64. package/dist/nodes/Confluence/actions/page/bodyEnvelope.js.map +1 -1
  65. package/dist/nodes/Confluence/actions/page/delete.operation.d.ts +5 -0
  66. package/dist/nodes/Confluence/actions/page/delete.operation.d.ts.map +1 -0
  67. package/dist/nodes/Confluence/actions/page/delete.operation.js +82 -0
  68. package/dist/nodes/Confluence/actions/page/delete.operation.js.map +1 -0
  69. package/dist/nodes/Confluence/actions/page/index.d.ts +4 -1
  70. package/dist/nodes/Confluence/actions/page/index.d.ts.map +1 -1
  71. package/dist/nodes/Confluence/actions/page/index.js +28 -1
  72. package/dist/nodes/Confluence/actions/page/index.js.map +1 -1
  73. package/dist/nodes/Confluence/actions/page/pageWrite.d.ts +17 -0
  74. package/dist/nodes/Confluence/actions/page/pageWrite.d.ts.map +1 -0
  75. package/dist/nodes/Confluence/actions/page/pageWrite.js +70 -0
  76. package/dist/nodes/Confluence/actions/page/pageWrite.js.map +1 -0
  77. package/dist/nodes/Confluence/actions/page/update.operation.d.ts +5 -0
  78. package/dist/nodes/Confluence/actions/page/update.operation.d.ts.map +1 -0
  79. package/dist/nodes/Confluence/actions/page/update.operation.js +72 -0
  80. package/dist/nodes/Confluence/actions/page/update.operation.js.map +1 -0
  81. package/dist/nodes/Confluence/actions/router.d.ts +2 -0
  82. package/dist/nodes/Confluence/actions/router.d.ts.map +1 -1
  83. package/dist/nodes/Confluence/actions/router.js +15 -1
  84. package/dist/nodes/Confluence/actions/router.js.map +1 -1
  85. package/dist/nodes/Confluence/transport/index.d.ts +6 -0
  86. package/dist/nodes/Confluence/transport/index.d.ts.map +1 -1
  87. package/dist/nodes/Confluence/transport/index.js +86 -10
  88. package/dist/nodes/Confluence/transport/index.js.map +1 -1
  89. package/dist/nodes/Files/ExtractFromFile/actions/moveTo.operation.d.ts.map +1 -1
  90. package/dist/nodes/Files/ExtractFromFile/actions/moveTo.operation.js +2 -2
  91. package/dist/nodes/Files/ExtractFromFile/actions/moveTo.operation.js.map +1 -1
  92. package/dist/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.d.ts +3 -0
  93. package/dist/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.d.ts.map +1 -0
  94. package/dist/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.js +10 -0
  95. package/dist/nodes/Files/ExtractFromFile/actions/parseIcsCalendar.js.map +1 -0
  96. package/dist/nodes/Form/Form.node.d.ts.map +1 -1
  97. package/dist/nodes/Form/Form.node.js +8 -0
  98. package/dist/nodes/Form/Form.node.js.map +1 -1
  99. package/dist/nodes/Form/interfaces.d.ts +1 -0
  100. package/dist/nodes/Form/interfaces.d.ts.map +1 -1
  101. package/dist/nodes/Form/interfaces.js.map +1 -1
  102. package/dist/nodes/Form/utils/formCompletionUtils.d.ts.map +1 -1
  103. package/dist/nodes/Form/utils/formCompletionUtils.js +8 -1
  104. package/dist/nodes/Form/utils/formCompletionUtils.js.map +1 -1
  105. package/dist/nodes/Form/utils/formNodeUtils.d.ts.map +1 -1
  106. package/dist/nodes/Form/utils/formNodeUtils.js +14 -3
  107. package/dist/nodes/Form/utils/formNodeUtils.js.map +1 -1
  108. package/dist/nodes/Form/utils/utils.d.ts +49 -9
  109. package/dist/nodes/Form/utils/utils.d.ts.map +1 -1
  110. package/dist/nodes/Form/utils/utils.js +309 -69
  111. package/dist/nodes/Form/utils/utils.js.map +1 -1
  112. package/dist/nodes/Form/v2/FormTriggerV2.node.d.ts +1 -1
  113. package/dist/nodes/Form/v2/FormTriggerV2.node.d.ts.map +1 -1
  114. package/dist/nodes/Form/v2/FormTriggerV2.node.js +0 -1
  115. package/dist/nodes/Form/v2/FormTriggerV2.node.js.map +1 -1
  116. package/dist/nodes/Google/Calendar/GoogleCalendar.node.d.ts.map +1 -1
  117. package/dist/nodes/Google/Calendar/GoogleCalendar.node.js +7 -0
  118. package/dist/nodes/Google/Calendar/GoogleCalendar.node.js.map +1 -1
  119. package/dist/nodes/Google/Drive/GoogleDriveTrigger.node.d.ts.map +1 -1
  120. package/dist/nodes/Google/Drive/GoogleDriveTrigger.node.js +82 -7
  121. package/dist/nodes/Google/Drive/GoogleDriveTrigger.node.js.map +1 -1
  122. package/dist/nodes/Google/Drive/v2/actions/file/download.operation.js +1 -1
  123. package/dist/nodes/Google/Drive/v2/actions/file/download.operation.js.map +1 -1
  124. package/dist/nodes/Grist/Grist.node.d.ts.map +1 -1
  125. package/dist/nodes/Grist/Grist.node.js +60 -2
  126. package/dist/nodes/Grist/Grist.node.js.map +1 -1
  127. package/dist/nodes/Grist/OperationDescription.d.ts.map +1 -1
  128. package/dist/nodes/Grist/OperationDescription.js +83 -3
  129. package/dist/nodes/Grist/OperationDescription.js.map +1 -1
  130. package/dist/nodes/Grist/types.d.ts +10 -0
  131. package/dist/nodes/Grist/types.d.ts.map +1 -1
  132. package/dist/nodes/MessageAnAgent/MessageAnAgent.node.d.ts.map +1 -1
  133. package/dist/nodes/MessageAnAgent/MessageAnAgent.node.js +5 -3
  134. package/dist/nodes/MessageAnAgent/MessageAnAgent.node.js.map +1 -1
  135. package/dist/nodes/MessageAnAgent/shared.d.ts.map +1 -1
  136. package/dist/nodes/MessageAnAgent/shared.js +146 -4
  137. package/dist/nodes/MessageAnAgent/shared.js.map +1 -1
  138. package/dist/nodes/MessageAnAgent/v2/MessageAnAgentV2.node.d.ts.map +1 -1
  139. package/dist/nodes/MessageAnAgent/v2/MessageAnAgentV2.node.js +4 -3
  140. package/dist/nodes/MessageAnAgent/v2/MessageAnAgentV2.node.js.map +1 -1
  141. package/dist/nodes/Microsoft/SharePoint/MicrosoftSharePoint.node.d.ts.map +1 -1
  142. package/dist/nodes/Microsoft/SharePoint/MicrosoftSharePoint.node.js +3 -8
  143. package/dist/nodes/Microsoft/SharePoint/MicrosoftSharePoint.node.js.map +1 -1
  144. package/dist/nodes/MongoDb/GenericFunctions.d.ts +1 -0
  145. package/dist/nodes/MongoDb/GenericFunctions.d.ts.map +1 -1
  146. package/dist/nodes/MongoDb/GenericFunctions.js +15 -2
  147. package/dist/nodes/MongoDb/GenericFunctions.js.map +1 -1
  148. package/dist/nodes/MongoDb/MongoDb.node.d.ts.map +1 -1
  149. package/dist/nodes/MongoDb/MongoDb.node.js +32 -19
  150. package/dist/nodes/MongoDb/MongoDb.node.js.map +1 -1
  151. package/dist/nodes/PagerDuty/GenericFunctions.d.ts.map +1 -1
  152. package/dist/nodes/PagerDuty/GenericFunctions.js +5 -2
  153. package/dist/nodes/PagerDuty/GenericFunctions.js.map +1 -1
  154. package/dist/nodes/Transform/Aggregate/Aggregate.node.d.ts.map +1 -1
  155. package/dist/nodes/Transform/Aggregate/Aggregate.node.js +1 -4
  156. package/dist/nodes/Transform/Aggregate/Aggregate.node.js.map +1 -1
  157. package/dist/nodes/Transform/SplitOut/utils.d.ts.map +1 -1
  158. package/dist/nodes/Transform/SplitOut/utils.js +2 -4
  159. package/dist/nodes/Transform/SplitOut/utils.js.map +1 -1
  160. package/dist/nodes/Transform/utils/utils.d.ts +6 -0
  161. package/dist/nodes/Transform/utils/utils.d.ts.map +1 -1
  162. package/dist/nodes/Transform/utils/utils.js +15 -1
  163. package/dist/nodes/Transform/utils/utils.js.map +1 -1
  164. package/dist/nodes/Wait/Wait.node.d.ts +1 -1
  165. package/dist/types/credentials.json +3 -3
  166. package/dist/types/nodes.json +9 -8
  167. package/package.json +13 -13
@@ -37,7 +37,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.isFormConnected = exports.validateResponseModeConfiguration = exports.prepareFormFields = exports.handleNewlines = exports.getNodeReference = void 0;
40
- exports.isFormOAuth2Enabled = isFormOAuth2Enabled;
41
40
  exports.sanitizeHtml = sanitizeHtml;
42
41
  exports.sanitizeCustomCss = sanitizeCustomCss;
43
42
  exports.validateSafeRedirectUrl = validateSafeRedirectUrl;
@@ -46,8 +45,11 @@ exports.prepareFormData = prepareFormData;
46
45
  exports.addFormResponseDataToReturnItem = addFormResponseDataToReturnItem;
47
46
  exports.prepareFormReturnItem = prepareFormReturnItem;
48
47
  exports.renderForm = renderForm;
48
+ exports.getHostNavigationPath = getHostNavigationPath;
49
+ exports.respondIfCredentialsNotReady = respondIfCredentialsNotReady;
49
50
  exports.generateFormUserAuthToken = generateFormUserAuthToken;
50
51
  exports.verifyFormUserAuthToken = verifyFormUserAuthToken;
52
+ exports.setFormAuthCookie = setFormAuthCookie;
51
53
  exports.validateFormPageAuth = validateFormPageAuth;
52
54
  exports.formShellSummaryText = formShellSummaryText;
53
55
  exports.buildFormShellViewModel = buildFormShellViewModel;
@@ -77,6 +79,9 @@ const interfaces_1 = require("../interfaces");
77
79
  const FORM_USER_AUTH_TOKEN_TTL_SECONDS = 60 * 60;
78
80
  const getNodeReference = (nodeName) => `$(${JSON.stringify(nodeName)})`;
79
81
  exports.getNodeReference = getNodeReference;
82
+ function isOptionalString(value) {
83
+ return value === undefined || typeof value === 'string';
84
+ }
80
85
  function isFormUserAuthClaims(value) {
81
86
  if (typeof value !== 'object' || value === null)
82
87
  return false;
@@ -86,10 +91,17 @@ function isFormUserAuthClaims(value) {
86
91
  typeof c.firstName === 'string' &&
87
92
  typeof c.lastName === 'string' &&
88
93
  typeof c.nid === 'string' &&
89
- typeof c.wid === 'string');
94
+ typeof c.wid === 'string' &&
95
+ isOptionalString(c.wfid) &&
96
+ isOptionalString(c.eid));
90
97
  }
91
- function isFormOAuth2Enabled() {
92
- return process.env.N8N_ENV_FEAT_FORM_TRIGGER_OAUTH2 === 'true';
98
+ function userFromFormUserAuthClaims(claims) {
99
+ return {
100
+ id: claims.sub,
101
+ email: claims.email,
102
+ firstName: claims.firstName,
103
+ lastName: claims.lastName,
104
+ };
93
105
  }
94
106
  function sanitizeHtml(text) {
95
107
  return (0, sanitize_html_1.default)(text, {
@@ -231,7 +243,7 @@ function getFieldIdentifier(field, nodeVersion) {
231
243
  }
232
244
  return field.fieldLabel ?? field.fieldName ?? '';
233
245
  }
234
- function prepareFormData({ formTitle, formDescription, formSubmittedHeader, formSubmittedText, redirectUrl, formFields, testRun, query, instanceId, useResponseData, appendAttribution = true, buttonLabel, customCss, nodeVersion, authToken, shellInner, hasAuthenticatedSubmitter, }) {
246
+ function prepareFormData({ formTitle, formDescription, formSubmittedHeader, formSubmittedText, redirectUrl, formFields, testRun, query, instanceId, useResponseData, appendAttribution = true, buttonLabel, customCss, nodeVersion, authToken, shellInner, hasAuthenticatedSubmitter, hostNavigationPath, }) {
235
247
  const utm_campaign = instanceId ? `&utm_campaign=${instanceId}` : '';
236
248
  const n8nWebsiteLink = `https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger${utm_campaign}`;
237
249
  if (formSubmittedText === undefined) {
@@ -255,6 +267,7 @@ function prepareFormData({ formTitle, formDescription, formSubmittedHeader, form
255
267
  shellInner: shellInner || undefined,
256
268
  // Only set for forms that can be gated, so the rest never ship the client handling.
257
269
  hasAuthenticatedSubmitter: hasAuthenticatedSubmitter || undefined,
270
+ hostNavigationPath: hostNavigationPath || undefined,
258
271
  };
259
272
  if (redirectUrl) {
260
273
  const safeUrl = validateSafeRedirectUrl(redirectUrl);
@@ -487,6 +500,10 @@ function renderForm({ context, res, formTitle, formDescription, formFields, resp
487
500
  catch (error) { }
488
501
  }
489
502
  formFields = (0, exports.prepareFormFields)(formFields);
503
+ // A follow-up page the shell navigated its frame to is the shell's inner render
504
+ // just like the flagged page-1 request — it must render the readiness listener
505
+ // and leave the submit button's enabled state to the shell's connect panel.
506
+ const inShell = isHostedInFormShell(context.getRequestObject(), shellInner);
490
507
  const data = prepareFormData({
491
508
  formTitle,
492
509
  formDescription,
@@ -502,8 +519,9 @@ function renderForm({ context, res, formTitle, formDescription, formFields, resp
502
519
  customCss,
503
520
  nodeVersion: context.getNode().typeVersion,
504
521
  authToken,
505
- shellInner,
522
+ shellInner: inShell,
506
523
  hasAuthenticatedSubmitter,
524
+ hostNavigationPath: getHostNavigationPath(context, inShell),
507
525
  });
508
526
  if (!(0, n8n_core_1.isFormHtmlSandboxingDisabled)()) {
509
527
  res.setHeader('Content-Security-Policy', (0, n8n_core_1.getHtmlSandboxCSP)());
@@ -533,10 +551,75 @@ function buildAbsoluteFormUrl(req) {
533
551
  const host = headerValue('x-forwarded-host') ?? req.headers.host ?? '';
534
552
  return `${protocol}://${host}${req.originalUrl}`;
535
553
  }
554
+ /**
555
+ * True when this render is the hosting shell's frame — either the shell's own
556
+ * inner render (flagged in the URL) or a follow-up page the shell navigated its
557
+ * frame to. Only then may the form hand its page navigations to the host.
558
+ *
559
+ * A follow-up page is recognised by the `n8nShellHosted` flag the shell appends
560
+ * to every URL it moves its frame to. Being told outright, rather than inferred
561
+ * from `Sec-Fetch-*`, keeps delegation working where those headers never arrive
562
+ * (a proxy that strips them) and keeps a same-origin frame that is *not* the
563
+ * shell from being treated as one.
564
+ *
565
+ * Where those headers do arrive they still have to agree — a same-origin frame —
566
+ * so a top-level document, or a form embedded on someone else's site, keeps
567
+ * navigating itself even with the flag in its URL.
568
+ *
569
+ * Nothing rests on the flag being unguessable: a frame that sets it on itself
570
+ * gains only the ability to ask its own parent to move it, and the shell accepts
571
+ * that ask for this form's own pages alone.
572
+ */
573
+ function isHostedInFormShell(req, shellInner) {
574
+ if (shellInner)
575
+ return true;
576
+ const secFetchDest = req.headers?.['sec-fetch-dest'];
577
+ const secFetchSite = req.headers?.['sec-fetch-site'];
578
+ return (req.query?.n8nShellHosted === '1' &&
579
+ (secFetchDest === undefined || secFetchDest === 'iframe') &&
580
+ (secFetchSite === undefined || secFetchSite === 'same-origin'));
581
+ }
582
+ /**
583
+ * The path prefix this render may ask the hosting shell to navigate to, or
584
+ * `undefined` when the render isn't in the shell's frame and so keeps navigating
585
+ * itself.
586
+ */
587
+ function getHostNavigationPath(context, shellInner) {
588
+ if (!isHostedInFormShell(context.getRequestObject(), shellInner))
589
+ return undefined;
590
+ return getFormWaitingBasePath(context) ?? undefined;
591
+ }
536
592
  const isFormConnected = (nodes) => {
537
593
  return nodes.some((n) => n.type === n8n_workflow_1.FORM_NODE_TYPE || (n.type === n8n_workflow_1.WAIT_NODE_TYPE && n.parameters?.resume === 'form'));
538
594
  };
539
595
  exports.isFormConnected = isFormConnected;
596
+ /**
597
+ * Submit-time credential readiness gate, shared by the trigger's POST and the
598
+ * Form node's POST. Answers 428 with the missing-credential list when the
599
+ * submitter's required accounts aren't all connected, 503 when the check itself
600
+ * fails. Returns true when a response was sent and the submission must not
601
+ * proceed. A no-op (`undefined` readiness) outside the dynamic-credentials flow.
602
+ */
603
+ async function respondIfCredentialsNotReady(context, res) {
604
+ let readiness;
605
+ try {
606
+ readiness = await context.checkTriggerCredentialStatus();
607
+ }
608
+ catch (error) {
609
+ // Fail closed. Throwing here is swallowed by the webhook layer, which then
610
+ // enqueues the execution anyway — exactly the doomed run we're preventing.
611
+ context.logger.error('Form submit credential readiness check failed', { error });
612
+ res.status(503).json({ status: 'credential_readiness_check_failed' });
613
+ return true;
614
+ }
615
+ if (readiness && !readiness.readyToExecute) {
616
+ // 428, matching the webhook trigger's gate (webhook-helpers.ts) so all
617
+ // trigger paths answer an unconnected credential the same way.
618
+ res.status(428).json((0, n8n_workflow_1.buildCredentialConnectionsRequiredResponse)(readiness));
619
+ return true;
620
+ }
621
+ return false;
622
+ }
540
623
  /**
541
624
  * Generate a form auth token for n8nUserAuth. The token embeds the user info
542
625
  * in a signed JWT so the POST handler can authenticate the submission without
@@ -545,10 +628,12 @@ exports.isFormConnected = isFormConnected;
545
628
  * cookie is `SameSite=Lax`).
546
629
  *
547
630
  * The `nid` and `wid` claims bind the token to a specific node + webhook,
548
- * preventing replay across forms. Signed with HS256 using the instance's
549
- * hmac signature secret.
631
+ * preventing replay across forms. `wfid`/`eid` additionally bind it to the
632
+ * workflow and — for tokens minted while a run is in progress — that run, which
633
+ * is what lets the same token be accepted as the form page auth cookie. Signed
634
+ * with HS256 using the instance's hmac signature secret.
550
635
  */
551
- function generateFormUserAuthToken(node, user) {
636
+ function generateFormUserAuthToken(node, user, binding) {
552
637
  const secret = di_1.Container.get(n8n_core_1.InstanceSettings).hmacSignatureSecret;
553
638
  const payload = {
554
639
  sub: user.id,
@@ -557,6 +642,8 @@ function generateFormUserAuthToken(node, user) {
557
642
  lastName: user.lastName,
558
643
  nid: node.id,
559
644
  wid: node.webhookId ?? '',
645
+ ...(binding.workflowId ? { wfid: binding.workflowId } : {}),
646
+ ...(binding.executionId ? { eid: binding.executionId } : {}),
560
647
  };
561
648
  return jsonwebtoken_1.default.sign(payload, secret, {
562
649
  algorithm: 'HS256',
@@ -564,11 +651,27 @@ function generateFormUserAuthToken(node, user) {
564
651
  });
565
652
  }
566
653
  /**
567
- * Verify a form auth token issued by `generateFormUserAuthToken`. Returns the
568
- * encoded user on success or `null` on any failure (bad format, expired,
569
- * wrong signature, wrong node). The caller decides how to surface the failure.
654
+ * Verify a form auth token issued by `generateFormUserAuthToken` and presented
655
+ * in the `x-auth-token` header. Returns the encoded user on success or `null` on
656
+ * any failure (bad format, expired, wrong signature, wrong node, wrong
657
+ * execution). The caller decides how to surface the failure.
658
+ *
659
+ * A token carrying an `eid` is only accepted for that execution. Tokens minted
660
+ * before a run existed carry none and stay valid for the node they name.
570
661
  */
571
- function verifyFormUserAuthToken(token, node) {
662
+ function verifyFormUserAuthToken(token, node, executionId) {
663
+ const claims = decodeFormUserAuthClaims(token);
664
+ if (!claims)
665
+ return null;
666
+ if (claims.nid !== node.id)
667
+ return null;
668
+ if (claims.wid !== (node.webhookId ?? ''))
669
+ return null;
670
+ if (claims.eid !== undefined && claims.eid !== executionId)
671
+ return null;
672
+ return userFromFormUserAuthClaims(claims);
673
+ }
674
+ function decodeFormUserAuthClaims(token) {
572
675
  const secret = di_1.Container.get(n8n_core_1.InstanceSettings).hmacSignatureSecret;
573
676
  let claims;
574
677
  try {
@@ -577,18 +680,24 @@ function verifyFormUserAuthToken(token, node) {
577
680
  catch {
578
681
  return null;
579
682
  }
580
- if (!isFormUserAuthClaims(claims))
683
+ return isFormUserAuthClaims(claims) ? claims : null;
684
+ }
685
+ /**
686
+ * Verify a form auth token presented as the form page auth cookie. Unlike the
687
+ * `x-auth-token` variant this is not bound to a single node: the cookie rides
688
+ * along a navigation to whichever node serves the next page of the same form,
689
+ * so it is bound to the workflow (`wfid`) and, once a run exists, to that run
690
+ * (`eid`). Tokens predating those claims are not accepted here.
691
+ */
692
+ function verifyFormPageAuthCookie(token, workflowId, executionId) {
693
+ const claims = decodeFormUserAuthClaims(token);
694
+ if (!claims)
581
695
  return null;
582
- if (claims.nid !== node.id)
696
+ if (!claims.wfid || !workflowId || claims.wfid !== workflowId)
583
697
  return null;
584
- if (claims.wid !== (node.webhookId ?? ''))
698
+ if (claims.eid !== undefined && claims.eid !== executionId)
585
699
  return null;
586
- return {
587
- id: claims.sub,
588
- email: claims.email,
589
- firstName: claims.firstName,
590
- lastName: claims.lastName,
591
- };
700
+ return userFromFormUserAuthClaims(claims);
592
701
  }
593
702
  function trimTrailingSlash(url) {
594
703
  return url.endsWith('/') ? url.slice(0, -1) : url;
@@ -598,16 +707,21 @@ function trimTrailingSlash(url) {
598
707
  // sandboxed form page. The token is otherwise already embedded in the form HTML
599
708
  // (the page sends it back as `x-auth-token` on POST), so this is not a new exposure.
600
709
  const FORM_OAUTH_COOKIE_NAME = 'n8n-form-oauth';
601
- function formOAuthCookieOptions(req, resourceUrl) {
602
- // Derive `secure` from the request scheme (honouring x-forwarded-proto, as
603
- // buildAbsoluteFormUrl does) rather than config, so the cookie is actually sent
604
- // back on the follow-up GET over http in dev while staying Secure over https.
710
+ /**
711
+ * Derive `secure` from the request scheme (honouring x-forwarded-proto, as
712
+ * buildAbsoluteFormUrl does) rather than config, so form cookies are actually
713
+ * sent back over http in dev while staying Secure over https.
714
+ */
715
+ function isSecureRequest(req) {
605
716
  const forwardedProto = req.headers['x-forwarded-proto'];
606
717
  const proto = (typeof forwardedProto === 'string' ? forwardedProto.trim() : '') || req.protocol;
718
+ return proto === 'https';
719
+ }
720
+ function formOAuthCookieOptions(req, resourceUrl) {
607
721
  return {
608
722
  httpOnly: true,
609
723
  sameSite: 'lax', // must be Lax: sent on our own top-level 302 → GET
610
- secure: proto === 'https',
724
+ secure: isSecureRequest(req),
611
725
  path: new URL(resourceUrl).pathname, // scope the bearer token to this form
612
726
  };
613
727
  }
@@ -617,13 +731,137 @@ function setFormOAuthToken(res, req, resourceUrl, token) {
617
731
  maxAge: 60_000, // one redirect hop; short by design
618
732
  });
619
733
  }
734
+ /**
735
+ * Decode a cookie value, or `null` when it isn't valid percent-encoding. A value
736
+ * we can't read is treated as no cookie at all — the caller's other checks still
737
+ * get their turn — rather than throwing out of the request.
738
+ */
739
+ function decodeCookieValue(value) {
740
+ try {
741
+ return decodeURIComponent(value.trim());
742
+ }
743
+ catch {
744
+ return null;
745
+ }
746
+ }
620
747
  function readFormOAuthToken(req) {
621
748
  const match = (req.headers.cookie ?? '').match(/(?:^|;\s*)n8n-form-oauth=([^;]+)/);
622
- return match ? decodeURIComponent(match[1].trim()) : null;
749
+ return match ? decodeCookieValue(match[1]) : null;
623
750
  }
624
751
  function clearFormOAuthToken(res, req, resourceUrl) {
625
752
  res.clearCookie(FORM_OAUTH_COOKIE_NAME, formOAuthCookieOptions(req, resourceUrl));
626
753
  }
754
+ // Carries the submitter's identity to the follow-up pages of a multi-page form.
755
+ // Those pages are reached by a navigation, which can present neither the
756
+ // `x-auth-token` header nor — from a sandboxed, opaque-origin form document —
757
+ // the `n8n-auth` session cookie.
758
+ //
759
+ // The name carries the token's binding, so forms open in other tabs don't
760
+ // overwrite each other's cookie: pages of a run use the run's id, and the
761
+ // trigger — rendered before any run exists — uses the workflow's. Duplicated in
762
+ // `packages/cli/src/constants.ts` (prefix only); keep both sides in step.
763
+ const FORM_AUTH_COOKIE_PREFIX = 'n8n-form-auth';
764
+ function getFormAuthCookieName(binding) {
765
+ return binding.executionId
766
+ ? `${FORM_AUTH_COOKIE_PREFIX}-ex-${binding.executionId}`
767
+ : `${FORM_AUTH_COOKIE_PREFIX}-wf-${binding.workflowId ?? ''}`;
768
+ }
769
+ /**
770
+ * Value of the request's cookie with exactly this name, or `null`. Parses the
771
+ * raw header (the webhook path may bypass cookie-parser) by splitting rather
772
+ * than a regex, since the name embeds a workflow or execution id.
773
+ */
774
+ function readCookieValue(req, name) {
775
+ for (const part of (req.headers.cookie ?? '').split(';')) {
776
+ const separator = part.indexOf('=');
777
+ if (separator === -1 || part.slice(0, separator).trim() !== name)
778
+ continue;
779
+ return decodeCookieValue(part.slice(separator + 1));
780
+ }
781
+ return null;
782
+ }
783
+ /**
784
+ * Path the form-waiting endpoint is served under, or `null` when it can't be
785
+ * derived. It is configurable and the instance may sit behind a path prefix, so
786
+ * derive it rather than assume it: `$execution.resumeFormUrl` is
787
+ * `<formWaitingBaseUrl>/<executionId>`, and dropping the last segment leaves the
788
+ * base path.
789
+ */
790
+ function getFormWaitingBasePath(context) {
791
+ try {
792
+ const resumeFormUrl = context.evaluateExpression('{{ $execution.resumeFormUrl }}');
793
+ const { pathname } = new URL(resumeFormUrl);
794
+ return pathname.slice(0, pathname.lastIndexOf('/')) || null;
795
+ }
796
+ catch {
797
+ return null;
798
+ }
799
+ }
800
+ /**
801
+ * Hand the follow-up pages of a multi-page form their own auth token, scoped to
802
+ * the form-waiting path. Re-set on every page render, so a long form's later
803
+ * pages get a token that is still within its TTL.
804
+ */
805
+ function setFormAuthCookie(context, token, binding) {
806
+ const req = context.getRequestObject();
807
+ context.getResponseObject().cookie(getFormAuthCookieName(binding), token, {
808
+ httpOnly: true,
809
+ // Lax, and only ever sent on a navigation the shell (or the top-level page)
810
+ // initiates from the real origin.
811
+ sameSite: 'lax',
812
+ secure: isSecureRequest(req),
813
+ // Nothing narrower is derivable when the base path is unknown; the cookie is
814
+ // httpOnly and expires with the token either way.
815
+ path: getFormWaitingBasePath(context) ?? '/',
816
+ maxAge: FORM_USER_AUTH_TOKEN_TTL_SECONDS * 1000,
817
+ });
818
+ }
819
+ /**
820
+ * Resolve the submitter from the form page auth cookie, or `null` when it is
821
+ * absent or doesn't verify for the workflow/execution being served.
822
+ */
823
+ function readFormAuthCookieUser(context) {
824
+ const req = context.getRequestObject();
825
+ const workflowId = context.getWorkflow().id;
826
+ const executionId = context.getExecutionId();
827
+ if (!workflowId)
828
+ return null;
829
+ // The run's own cookie first; the trigger's workflow-scoped one covers the
830
+ // first hop into the run, made before the run existed.
831
+ const candidateNames = [
832
+ getFormAuthCookieName({ workflowId, executionId }),
833
+ getFormAuthCookieName({ workflowId }),
834
+ ];
835
+ for (const name of candidateNames) {
836
+ const token = readCookieValue(req, name);
837
+ if (!token)
838
+ continue;
839
+ const user = verifyFormPageAuthCookie(token, workflowId, executionId);
840
+ if (user)
841
+ return user;
842
+ }
843
+ return null;
844
+ }
845
+ /**
846
+ * True when the request also carries a session that belongs to someone other
847
+ * than the form cookie's user — a sign-out and sign-in as a different person in
848
+ * the same browser. The live session wins: the form cookie is dropped and the
849
+ * request re-authenticates through the normal path. An unusable session cookie
850
+ * names nobody, so it leaves the form cookie alone.
851
+ */
852
+ async function isSessionForDifferentUser(context, formCookieUser) {
853
+ const req = context.getRequestObject();
854
+ const match = (req.headers.cookie ?? '').match(/(?:^|;\s*)n8n-auth=([^;]+)/);
855
+ if (!match)
856
+ return false;
857
+ try {
858
+ const sessionUser = await context.validateCookieAuth(match[1].trim());
859
+ return sessionUser.id !== formCookieUser.id;
860
+ }
861
+ catch {
862
+ return false;
863
+ }
864
+ }
627
865
  /**
628
866
  * Authenticate an `n8nUserAuth` request via:
629
867
  * 1. the `n8n-auth` cookie (sent on top-level GET when the user is logged in), or
@@ -634,9 +872,9 @@ function clearFormOAuthToken(res, req, resourceUrl) {
634
872
  * (302 to `/signin` on GET, 401 on POST) and returns `null` — the caller
635
873
  * must abort with `noWebhookResponse`.
636
874
  */
637
- async function authenticateFormUserOrRespond(context, oauth2Enabled = false) {
875
+ async function authenticateFormUserOrRespond(context, runOAuth2Flow = false) {
638
876
  const req = context.getRequestObject();
639
- if (oauth2Enabled) {
877
+ if (runOAuth2Flow) {
640
878
  const res = context.getResponseObject();
641
879
  const url = context.getWebhookResourceUrl('default');
642
880
  if (!url) {
@@ -757,7 +995,7 @@ async function authenticateFormUserOrRespond(context, oauth2Enabled = false) {
757
995
  }
758
996
  const formToken = req.headers['x-auth-token'];
759
997
  if (typeof formToken === 'string' && formToken) {
760
- const user = verifyFormUserAuthToken(formToken, context.getNode());
998
+ const user = verifyFormUserAuthToken(formToken, context.getNode(), context.getExecutionId());
761
999
  if (user)
762
1000
  return { user, token: null };
763
1001
  }
@@ -778,12 +1016,27 @@ async function authenticateFormUserOrRespond(context, oauth2Enabled = false) {
778
1016
  * Multi-step Form/Wait nodes inherit `authentication` from the upstream
779
1017
  * Form Trigger. This wrapper short-circuits when n8nUserAuth isn't in use.
780
1018
  *
1019
+ * A page is reached by navigating to it, which cannot carry an `x-auth-token`
1020
+ * header, so a GET is authenticated from the form page auth cookie first. Every
1021
+ * other request — and any GET whose cookie is missing, doesn't verify, or names
1022
+ * someone other than the session on the same request — falls through to the
1023
+ * session cookie / `x-auth-token` checks, unchanged.
1024
+ *
1025
+ * Form pages never take the OAuth2 branch: the OAuth2 token's audience is the
1026
+ * trigger's URL, which a page's own resource URL is not.
1027
+ *
781
1028
  * Returns `{ authedUser }` on success, `{ responded: true }` after sending a
782
1029
  * 302/401 on failure, or `{}` if the trigger doesn't require auth.
783
1030
  */
784
1031
  async function validateFormPageAuth(context, triggerAuthentication) {
785
1032
  if (triggerAuthentication !== 'n8nUserAuth')
786
1033
  return {};
1034
+ if (context.getRequestObject().method === 'GET') {
1035
+ const cookieUser = readFormAuthCookieUser(context);
1036
+ if (cookieUser && !(await isSessionForDifferentUser(context, cookieUser))) {
1037
+ return { authedUser: cookieUser };
1038
+ }
1039
+ }
787
1040
  const { user } = (await authenticateFormUserOrRespond(context, false)) ?? {};
788
1041
  return user ? { authedUser: user } : { responded: true };
789
1042
  }
@@ -797,7 +1050,7 @@ async function validateFormPageAuth(context, triggerAuthentication) {
797
1050
  * links — OUTSIDE the author-scriptable iframe DOM (the security win over
798
1051
  * connecting inside the form). It carries a live session, so it refuses framing.
799
1052
  */
800
- function renderFormShell({ res, req, formTitle, resourceUrl, credentials, submitterEmail, }) {
1053
+ function renderFormShell({ res, req, formTitle, resourceUrl, credentials, submitterEmail, formWaitingPath, }) {
801
1054
  // The iframe loads the same form via a real URL (so the form's relative POST
802
1055
  // works exactly as it does top-level today) flagged as the inner render.
803
1056
  const inner = new URL(buildAbsoluteFormUrl(req));
@@ -807,6 +1060,8 @@ function renderFormShell({ res, req, formTitle, resourceUrl, credentials, submit
807
1060
  formTitle,
808
1061
  iframeSrc: `${inner.pathname}${inner.search}`,
809
1062
  resourceUrl,
1063
+ // The only paths the shell will move its frame to on the form's request.
1064
+ formWaitingPath,
810
1065
  ...buildFormShellViewModel(credentials, submitterEmail),
811
1066
  });
812
1067
  }
@@ -886,7 +1141,7 @@ async function formWebhook(context, authProperty = interfaces_1.FORM_TRIGGER_AUT
886
1141
  let oAuth2Token;
887
1142
  if (node.typeVersion > 1) {
888
1143
  if (authentication === 'n8nUserAuth') {
889
- const { user, token } = (await authenticateFormUserOrRespond(context, isFormOAuth2Enabled())) ?? {};
1144
+ const { user, token } = (await authenticateFormUserOrRespond(context, true)) ?? {};
890
1145
  if (!user)
891
1146
  return { noWebhookResponse: true };
892
1147
  authedUser = user;
@@ -954,6 +1209,14 @@ async function formWebhook(context, authProperty = interfaces_1.FORM_TRIGGER_AUT
954
1209
  redirectUrl = undefined;
955
1210
  responseMode = 'responseNode';
956
1211
  }
1212
+ // A multi-page form hops to `/form-waiting/<execution>` by navigating, which
1213
+ // carries neither the `x-auth-token` header nor — from the hosting shell's
1214
+ // opaque-origin frame — the `n8n-auth` session cookie. Give the follow-up pages
1215
+ // their own path-scoped token so they can authenticate the same submitter.
1216
+ if (authentication === 'n8nUserAuth' && authedUser && hasNextPage) {
1217
+ const binding = { workflowId: context.getWorkflow().id };
1218
+ setFormAuthCookie(context, generateFormUserAuthToken(node, authedUser, binding), binding);
1219
+ }
957
1220
  // The inner render is the form loaded inside the hosting-shell iframe. Honor the
958
1221
  // flag only for iframe navigations (per Sec-Fetch-Dest, absent on older browsers)
959
1222
  // so a hand-typed URL can't skip the connect UI; the POST gate enforces regardless.
@@ -961,15 +1224,11 @@ async function formWebhook(context, authProperty = interfaces_1.FORM_TRIGGER_AUT
961
1224
  const shellInner = req.query.n8nShellInner === '1' && (secFetchDest === undefined || secFetchDest === 'iframe');
962
1225
  // Connect-before-submit: when the workflow needs the submitter's own
963
1226
  // (private) credentials, establish their identity from the OAuth2 token and
964
- // check readiness server-side. As long as the trigger requires any end-user
965
- // account — and we're not already rendering the inner iframe — wrap the form
966
- // in the hosting shell (form + connect panel). No-op unless OAuth2 form auth
967
- // and the dynamic-credentials module are both active.
968
- if (authentication === 'n8nUserAuth' &&
969
- authedUser &&
970
- isFormOAuth2Enabled() &&
971
- oAuth2Token &&
972
- !shellInner) {
1227
+ // check readiness server-side. If anything is still missing and we're not
1228
+ // already rendering the inner iframe — wrap the form in the hosting shell
1229
+ // (form + connect panel, submit disabled). No-op unless OAuth2 form auth and
1230
+ // the dynamic-credentials module are both active.
1231
+ if (authentication === 'n8nUserAuth' && authedUser && oAuth2Token && !shellInner) {
973
1232
  // Must name the endpoint actually being served (test vs production) — the
974
1233
  // OAuth2 token is bound to that resource, same as in the auth path above.
975
1234
  const resourceUrl = trimTrailingSlash(context.getWebhookResourceUrl('default') ?? '');
@@ -995,6 +1254,7 @@ async function formWebhook(context, authProperty = interfaces_1.FORM_TRIGGER_AUT
995
1254
  resourceUrl,
996
1255
  credentials: credentialStatus.credentials,
997
1256
  submitterEmail: authedUser?.email,
1257
+ formWaitingPath: getFormWaitingBasePath(context) ?? undefined,
998
1258
  });
999
1259
  return { noWebhookResponse: true };
1000
1260
  }
@@ -1003,15 +1263,9 @@ async function formWebhook(context, authProperty = interfaces_1.FORM_TRIGGER_AUT
1003
1263
  let authToken;
1004
1264
  if (node.typeVersion > 1) {
1005
1265
  if (authentication === 'n8nUserAuth' && authedUser) {
1006
- if (!isFormOAuth2Enabled()) {
1007
- // Cookies aren't sent on POST from the sandboxed form page
1008
- // (null origin + SameSite=Lax). Embed an HMAC token so the
1009
- // POST handler can re-authenticate the user.
1010
- authToken = generateFormUserAuthToken(node, authedUser);
1011
- }
1012
- else {
1013
- authToken = oAuth2Token;
1014
- }
1266
+ // Cookies aren't sent on POST from the sandboxed form page (null origin +
1267
+ // SameSite=Lax), so the POST re-authenticates off this token.
1268
+ authToken = oAuth2Token;
1015
1269
  }
1016
1270
  else {
1017
1271
  authToken = await (0, utils_1.generateFormPostBasicAuthToken)(context, authProperty);
@@ -1043,25 +1297,11 @@ async function formWebhook(context, authProperty = interfaces_1.FORM_TRIGGER_AUT
1043
1297
  // iframe. It also works off the state at render time, so a required credential can
1044
1298
  // be revoked — or the page simply left open — between render and submit. Re-check
1045
1299
  // here, after identity establishment and before the execution is enqueued, so a
1046
- // stale page can't spawn a run that dies at credential resolution. Scoped to the
1047
- // trigger: the Wait node shares `formWebhook`, but its form resume continues an
1048
- // already-running execution.
1300
+ // stale page can't spawn a run that dies at credential resolution. The Form node
1301
+ // runs the same gate on its own POST (Form.node.ts); the Wait node shares
1302
+ // `formWebhook`, so scope this call site to the trigger.
1049
1303
  if (node.type === n8n_workflow_1.FORM_TRIGGER_NODE_TYPE) {
1050
- let readiness;
1051
- try {
1052
- readiness = await context.checkTriggerCredentialStatus();
1053
- }
1054
- catch (error) {
1055
- // Fail closed. Throwing here is swallowed by the webhook layer, which then
1056
- // enqueues the execution anyway — exactly the doomed run we're preventing.
1057
- context.logger.error('Form submit credential readiness check failed', { error });
1058
- res.status(503).json({ status: 'credential_readiness_check_failed' });
1059
- return { noWebhookResponse: true };
1060
- }
1061
- if (readiness && !readiness.readyToExecute) {
1062
- // 428, matching the webhook trigger's gate (webhook-helpers.ts) so both
1063
- // trigger paths answer an unconnected credential the same way.
1064
- res.status(428).json((0, n8n_workflow_1.buildCredentialConnectionsRequiredResponse)(readiness));
1304
+ if (await respondIfCredentialsNotReady(context, res)) {
1065
1305
  return { noWebhookResponse: true };
1066
1306
  }
1067
1307
  }