@oomol-lab/open-connector 1.7.0 → 1.8.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 (288) hide show
  1. package/README.md +4 -0
  2. package/assets/open-connector/catalog/apps/airtable.json +134 -0
  3. package/assets/open-connector/catalog/apps/aisa.json +916 -76
  4. package/assets/open-connector/catalog/apps/cal.json +24 -6
  5. package/assets/open-connector/catalog/apps/discordbot.json +2 -2
  6. package/assets/open-connector/catalog/apps/enormail.json +411 -0
  7. package/assets/open-connector/catalog/apps/feishu_app_bot.json +158 -0
  8. package/assets/open-connector/catalog/apps/github.json +275 -0
  9. package/assets/open-connector/catalog/apps/gitlab.json +125 -0
  10. package/assets/open-connector/catalog/apps/gmail.json +151 -0
  11. package/assets/open-connector/catalog/apps/googlecalendar.json +206 -0
  12. package/assets/open-connector/catalog/apps/googledrive.json +479 -0
  13. package/assets/open-connector/catalog/apps/googlesheets.json +144 -0
  14. package/assets/open-connector/catalog/apps/hithink_finance.json +6 -3
  15. package/assets/open-connector/catalog/apps/indexed.json +1115 -0
  16. package/assets/open-connector/catalog/apps/linear.json +116 -0
  17. package/assets/open-connector/catalog/apps/notion.json +142 -0
  18. package/assets/open-connector/catalog/apps/one_drive.json +185 -0
  19. package/assets/open-connector/catalog/apps/outlook.json +182 -0
  20. package/assets/open-connector/catalog/apps/shopify.json +282 -0
  21. package/assets/open-connector/catalog/apps/slack.json +182 -0
  22. package/assets/open-connector/catalog/apps/spotify.json +46 -43
  23. package/assets/open-connector/catalog/apps/stripe.json +96 -0
  24. package/assets/open-connector/catalog/apps/telegram.json +138 -0
  25. package/assets/open-connector/catalog/apps/weixin_store.json +543 -2
  26. package/assets/open-connector/catalog/apps/woocommerce.json +127 -0
  27. package/assets/open-connector/catalog/apps/zendesk.json +152 -0
  28. package/assets/open-connector/catalog/apps/zerotier.json +9 -9
  29. package/assets/open-connector/catalog/apps-index.json +1 -1
  30. package/assets/open-connector/migrations/0014_saas_project.sql +55 -0
  31. package/assets/open-connector/migrations/0015_saas_cleanup_runtime.sql +8 -0
  32. package/assets/open-connector/migrations/0016_trigger_policy.sql +1 -0
  33. package/assets/open-connector/migrations/0017_trigger_subscriptions.sql +17 -0
  34. package/assets/open-connector/migrations/postgresql/0014_saas_project.sql +55 -0
  35. package/assets/open-connector/migrations/postgresql/0015_saas_cleanup_runtime.sql +8 -0
  36. package/assets/open-connector/migrations/postgresql/0016_trigger_policy.sql +1 -0
  37. package/assets/open-connector/migrations/postgresql/0017_trigger_subscriptions.sql +17 -0
  38. package/package.json +2 -2
  39. package/src/connection-service.d.ts +40 -7
  40. package/src/connection-service.js +60 -18
  41. package/src/core/action-policy.d.ts +8 -1
  42. package/src/core/action-policy.js +51 -0
  43. package/src/core/cast.d.ts +3 -0
  44. package/src/core/cast.js +4 -1
  45. package/src/core/guarded-fetch.d.ts +5 -0
  46. package/src/core/guarded-fetch.js +13 -4
  47. package/src/core/remote-http.d.ts +33 -0
  48. package/src/core/remote-http.js +104 -0
  49. package/src/core/request.d.ts +1 -0
  50. package/src/core/request.js +7 -0
  51. package/src/core/types.d.ts +4 -0
  52. package/src/marketplace/marketplace-service.d.ts +0 -1
  53. package/src/marketplace/marketplace-service.js +80 -92
  54. package/src/mcp.js +19 -5
  55. package/src/oauth/oauth-flow-service.d.ts +8 -1
  56. package/src/oauth/oauth-flow-service.js +10 -4
  57. package/src/providers/action-contracts.generated.d.ts +4 -2
  58. package/src/providers/airtable/executors.d.ts +3 -0
  59. package/src/providers/airtable/executors.js +2 -0
  60. package/src/providers/airtable/trigger-on-record-changed.d.ts +2 -0
  61. package/src/providers/airtable/trigger-on-record-changed.definition.d.ts +4 -0
  62. package/src/providers/airtable/trigger-on-record-changed.definition.js +54 -0
  63. package/src/providers/airtable/trigger-on-record-changed.js +235 -0
  64. package/src/providers/aisa/executors.js +196 -22
  65. package/src/providers/cal/actions.js +5 -1
  66. package/src/providers/enormail/executors.d.ts +4 -0
  67. package/src/providers/enormail/executors.js +97 -0
  68. package/src/providers/feishu_app_bot/executors.d.ts +3 -0
  69. package/src/providers/feishu_app_bot/executors.js +2 -0
  70. package/src/providers/feishu_app_bot/trigger-config.d.ts +1 -0
  71. package/src/providers/feishu_app_bot/trigger-config.js +10 -0
  72. package/src/providers/feishu_app_bot/trigger-on-event.d.ts +2 -0
  73. package/src/providers/feishu_app_bot/trigger-on-event.definition.d.ts +4 -0
  74. package/src/providers/feishu_app_bot/trigger-on-event.definition.js +63 -0
  75. package/src/providers/feishu_app_bot/trigger-on-event.js +29 -0
  76. package/src/providers/feishu_app_bot/trigger-subscriptions.d.ts +5 -0
  77. package/src/providers/feishu_app_bot/trigger-subscriptions.js +70 -0
  78. package/src/providers/github/executors.d.ts +3 -0
  79. package/src/providers/github/executors.js +6 -0
  80. package/src/providers/github/trigger-on-repo-event.d.ts +2 -0
  81. package/src/providers/github/trigger-on-repo-event.definition.d.ts +4 -0
  82. package/src/providers/github/trigger-on-repo-event.definition.js +94 -0
  83. package/src/providers/github/trigger-on-repo-event.js +168 -0
  84. package/src/providers/github/trigger-watch-pull-request.d.ts +2 -0
  85. package/src/providers/github/trigger-watch-pull-request.definition.d.ts +4 -0
  86. package/src/providers/github/trigger-watch-pull-request.definition.js +49 -0
  87. package/src/providers/github/trigger-watch-pull-request.js +106 -0
  88. package/src/providers/gitlab/executors.d.ts +3 -0
  89. package/src/providers/gitlab/executors.js +2 -0
  90. package/src/providers/gitlab/trigger-on-project-event.d.ts +2 -0
  91. package/src/providers/gitlab/trigger-on-project-event.definition.d.ts +5 -0
  92. package/src/providers/gitlab/trigger-on-project-event.definition.js +63 -0
  93. package/src/providers/gitlab/trigger-on-project-event.js +186 -0
  94. package/src/providers/gmail/executors.d.ts +3 -0
  95. package/src/providers/gmail/executors.js +2 -0
  96. package/src/providers/gmail/trigger-on-message-received.d.ts +2 -0
  97. package/src/providers/gmail/trigger-on-message-received.definition.d.ts +4 -0
  98. package/src/providers/gmail/trigger-on-message-received.definition.js +74 -0
  99. package/src/providers/gmail/trigger-on-message-received.js +232 -0
  100. package/src/providers/googlecalendar/executors.d.ts +3 -0
  101. package/src/providers/googlecalendar/executors.js +2 -0
  102. package/src/providers/googlecalendar/trigger-on-event-changed.d.ts +2 -0
  103. package/src/providers/googlecalendar/trigger-on-event-changed.definition.d.ts +4 -0
  104. package/src/providers/googlecalendar/trigger-on-event-changed.definition.js +74 -0
  105. package/src/providers/googlecalendar/trigger-on-event-changed.js +233 -0
  106. package/src/providers/googledrive/executors.d.ts +3 -0
  107. package/src/providers/googledrive/executors.js +7 -0
  108. package/src/providers/googledrive/trigger-changes.d.ts +12 -0
  109. package/src/providers/googledrive/trigger-changes.definition.d.ts +7 -0
  110. package/src/providers/googledrive/trigger-changes.definition.js +101 -0
  111. package/src/providers/googledrive/trigger-changes.js +452 -0
  112. package/src/providers/googledrive/trigger-on-file-change.d.ts +2 -0
  113. package/src/providers/googledrive/trigger-on-file-change.definition.d.ts +4 -0
  114. package/src/providers/googledrive/trigger-on-file-change.definition.js +77 -0
  115. package/src/providers/googledrive/trigger-on-file-change.js +258 -0
  116. package/src/providers/googlesheets/executors.d.ts +3 -0
  117. package/src/providers/googlesheets/executors.js +2 -0
  118. package/src/providers/googlesheets/trigger-on-row-added.d.ts +2 -0
  119. package/src/providers/googlesheets/trigger-on-row-added.definition.d.ts +4 -0
  120. package/src/providers/googlesheets/trigger-on-row-added.definition.js +54 -0
  121. package/src/providers/googlesheets/trigger-on-row-added.js +234 -0
  122. package/src/providers/hithink_finance/runtime.js +1 -1
  123. package/src/providers/indexed/executors.d.ts +4 -0
  124. package/src/providers/indexed/executors.js +16 -0
  125. package/src/providers/indexed/runtime.d.ts +26 -0
  126. package/src/providers/indexed/runtime.js +129 -0
  127. package/src/providers/linear/executors.d.ts +3 -0
  128. package/src/providers/linear/executors.js +2 -0
  129. package/src/providers/linear/trigger-on-issue-changed.d.ts +2 -0
  130. package/src/providers/linear/trigger-on-issue-changed.definition.d.ts +5 -0
  131. package/src/providers/linear/trigger-on-issue-changed.definition.js +43 -0
  132. package/src/providers/linear/trigger-on-issue-changed.js +215 -0
  133. package/src/providers/notion/executors.d.ts +3 -0
  134. package/src/providers/notion/executors.js +2 -0
  135. package/src/providers/notion/trigger-on-database-page-event.d.ts +2 -0
  136. package/src/providers/notion/trigger-on-database-page-event.definition.d.ts +4 -0
  137. package/src/providers/notion/trigger-on-database-page-event.definition.js +63 -0
  138. package/src/providers/notion/trigger-on-database-page-event.js +293 -0
  139. package/src/providers/one_drive/executors.d.ts +3 -0
  140. package/src/providers/one_drive/executors.js +2 -0
  141. package/src/providers/one_drive/trigger-on-item-changed.d.ts +2 -0
  142. package/src/providers/one_drive/trigger-on-item-changed.definition.d.ts +4 -0
  143. package/src/providers/one_drive/trigger-on-item-changed.definition.js +63 -0
  144. package/src/providers/one_drive/trigger-on-item-changed.js +214 -0
  145. package/src/providers/outlook/executors.d.ts +3 -0
  146. package/src/providers/outlook/executors.js +2 -0
  147. package/src/providers/outlook/trigger-on-message-received.d.ts +2 -0
  148. package/src/providers/outlook/trigger-on-message-received.definition.d.ts +4 -0
  149. package/src/providers/outlook/trigger-on-message-received.definition.js +93 -0
  150. package/src/providers/outlook/trigger-on-message-received.js +226 -0
  151. package/src/providers/outlook_calendar/executors.js +6 -0
  152. package/src/providers/provider-loader.d.ts +5 -0
  153. package/src/providers/provider-loader.js +4 -0
  154. package/src/providers/registry.generated.js +2 -0
  155. package/src/providers/shopify/executors.d.ts +3 -0
  156. package/src/providers/shopify/executors.js +2 -0
  157. package/src/providers/shopify/trigger-on-shop-event.d.ts +2 -0
  158. package/src/providers/shopify/trigger-on-shop-event.definition.d.ts +4 -0
  159. package/src/providers/shopify/trigger-on-shop-event.definition.js +214 -0
  160. package/src/providers/shopify/trigger-on-shop-event.js +165 -0
  161. package/src/providers/slack/executors.d.ts +3 -0
  162. package/src/providers/slack/executors.js +2 -0
  163. package/src/providers/slack/trigger-on-message-posted.d.ts +2 -0
  164. package/src/providers/slack/trigger-on-message-posted.definition.d.ts +4 -0
  165. package/src/providers/slack/trigger-on-message-posted.definition.js +80 -0
  166. package/src/providers/slack/trigger-on-message-posted.js +176 -0
  167. package/src/providers/spotify/runtime.js +15 -4
  168. package/src/providers/stripe/executors.d.ts +3 -0
  169. package/src/providers/stripe/executors.js +2 -0
  170. package/src/providers/stripe/trigger-on-event.d.ts +2 -0
  171. package/src/providers/stripe/trigger-on-event.definition.d.ts +4 -0
  172. package/src/providers/stripe/trigger-on-event.definition.js +43 -0
  173. package/src/providers/stripe/trigger-on-event.js +212 -0
  174. package/src/providers/telegram/executors.d.ts +3 -0
  175. package/src/providers/telegram/executors.js +2 -0
  176. package/src/providers/telegram/trigger-on-update.d.ts +2 -0
  177. package/src/providers/telegram/trigger-on-update.definition.d.ts +5 -0
  178. package/src/providers/telegram/trigger-on-update.definition.js +78 -0
  179. package/src/providers/telegram/trigger-on-update.js +141 -0
  180. package/src/providers/weixin_store/access-token.d.ts +2 -1
  181. package/src/providers/weixin_store/access-token.js +15 -4
  182. package/src/providers/weixin_store/executors.js +97 -12
  183. package/src/providers/woocommerce/executors.d.ts +3 -0
  184. package/src/providers/woocommerce/executors.js +2 -0
  185. package/src/providers/woocommerce/trigger-on-store-event.d.ts +2 -0
  186. package/src/providers/woocommerce/trigger-on-store-event.definition.d.ts +4 -0
  187. package/src/providers/woocommerce/trigger-on-store-event.definition.js +55 -0
  188. package/src/providers/woocommerce/trigger-on-store-event.js +179 -0
  189. package/src/providers/zendesk/executors.d.ts +3 -0
  190. package/src/providers/zendesk/executors.js +2 -0
  191. package/src/providers/zendesk/trigger-on-event.d.ts +2 -0
  192. package/src/providers/zendesk/trigger-on-event.definition.d.ts +5 -0
  193. package/src/providers/zendesk/trigger-on-event.definition.js +101 -0
  194. package/src/providers/zendesk/trigger-on-event.js +188 -0
  195. package/src/saas/saas-cleanup-service.d.ts +26 -0
  196. package/src/saas/saas-cleanup-service.js +124 -0
  197. package/src/saas/saas-client.d.ts +86 -0
  198. package/src/saas/saas-client.js +369 -0
  199. package/src/saas/saas-execution-service.d.ts +15 -0
  200. package/src/saas/saas-execution-service.js +21 -0
  201. package/src/saas/saas-oauth-service.d.ts +25 -0
  202. package/src/saas/saas-oauth-service.js +197 -0
  203. package/src/saas/saas-project-service.d.ts +61 -0
  204. package/src/saas/saas-project-service.js +211 -0
  205. package/src/server/actions/action-idempotency.d.ts +1 -0
  206. package/src/server/actions/action-idempotency.js +1 -0
  207. package/src/server/actions/action-runner.d.ts +7 -0
  208. package/src/server/actions/action-runner.js +43 -7
  209. package/src/server/actions/run-log-summary.js +9 -0
  210. package/src/server/api/auth.d.ts +1 -0
  211. package/src/server/api/auth.js +13 -2
  212. package/src/server/api/cache-policy.js +4 -1
  213. package/src/server/api/connection-input.d.ts +7 -1
  214. package/src/server/api/connection-input.js +7 -1
  215. package/src/server/api/connection-routes.d.ts +3 -1
  216. package/src/server/api/connection-routes.js +21 -3
  217. package/src/server/api/http-utils.d.ts +4 -3
  218. package/src/server/api/oauth-completion-page.d.ts +124 -0
  219. package/src/server/api/oauth-completion-page.js +113 -18
  220. package/src/server/api/openapi.js +112 -14
  221. package/src/server/api/policy-input.js +6 -0
  222. package/src/server/api/runtime-api.d.ts +10 -2
  223. package/src/server/api/runtime-api.js +17 -4
  224. package/src/server/api/saas-completion-page.d.ts +2 -0
  225. package/src/server/api/saas-completion-page.js +103 -0
  226. package/src/server/connect-app.d.ts +5 -0
  227. package/src/server/connect-app.js +50 -0
  228. package/src/server/connect-server.d.ts +12 -0
  229. package/src/server/connect-server.js +147 -13
  230. package/src/server/connector-runtime.d.ts +3 -1
  231. package/src/server/connector-runtime.js +5 -2
  232. package/src/server/files/transit-files.js +16 -7
  233. package/src/server/proxy/proxy-runner.d.ts +5 -1
  234. package/src/server/proxy/proxy-runner.js +72 -26
  235. package/src/server/storage/connection-request-store.d.ts +41 -1
  236. package/src/server/storage/connection-request-store.js +245 -20
  237. package/src/server/storage/connection-store.d.ts +16 -0
  238. package/src/server/storage/connection-store.js +119 -0
  239. package/src/server/storage/node-runtime-database.js +3 -3
  240. package/src/server/storage/{postgres-migrations.d.ts → postgres/migrations.d.ts} +2 -2
  241. package/src/server/storage/{postgres-migrations.js → postgres/migrations.js} +1 -1
  242. package/src/server/storage/postgres/runtime-store.d.ts +43 -0
  243. package/src/server/storage/{postgres-runtime-store.js → postgres/runtime-store.js} +59 -94
  244. package/src/server/storage/runtime-database.d.ts +4 -0
  245. package/src/server/storage/runtime-sql.d.ts +1 -1
  246. package/src/server/storage/runtime-sql.js +2 -1
  247. package/src/server/storage/runtime-store.d.ts +1 -0
  248. package/src/server/storage/runtime-token-service.d.ts +2 -0
  249. package/src/server/storage/runtime-token-service.js +3 -0
  250. package/src/server/storage/saas-project-store.d.ts +77 -0
  251. package/src/server/storage/saas-project-store.js +273 -0
  252. package/src/server/storage/{sqlite-runtime-store.d.ts → sqlite/runtime-store.d.ts} +21 -25
  253. package/src/server/storage/{sqlite-runtime-store.js → sqlite/runtime-store.js} +100 -115
  254. package/src/server/storage/trigger-store.d.ts +16 -0
  255. package/src/server/storage/trigger-store.js +96 -0
  256. package/src/triggers/common/config.d.ts +2 -0
  257. package/src/triggers/common/config.js +17 -0
  258. package/src/triggers/common/configOptions.d.ts +13 -0
  259. package/src/triggers/common/configOptions.js +1 -0
  260. package/src/triggers/common/encoding.d.ts +3 -0
  261. package/src/triggers/common/encoding.js +19 -0
  262. package/src/triggers/common/integration.d.ts +115 -0
  263. package/src/triggers/common/integration.js +31 -0
  264. package/src/triggers/common/json.d.ts +6 -0
  265. package/src/triggers/common/json.js +91 -0
  266. package/src/triggers/common/poll.d.ts +41 -0
  267. package/src/triggers/common/poll.js +15 -0
  268. package/src/triggers/common/proxy.d.ts +14 -0
  269. package/src/triggers/common/proxy.js +1 -0
  270. package/src/triggers/common/types.d.ts +42 -0
  271. package/src/triggers/common/types.js +1 -0
  272. package/src/triggers/maintenance.d.ts +40 -0
  273. package/src/triggers/maintenance.js +120 -0
  274. package/src/triggers/metadata.d.ts +8 -0
  275. package/src/triggers/metadata.js +1 -0
  276. package/src/triggers/request.d.ts +33 -0
  277. package/src/triggers/request.js +52 -0
  278. package/src/triggers/resources.d.ts +12 -0
  279. package/src/triggers/resources.js +121 -0
  280. package/src/triggers/signature.d.ts +4 -0
  281. package/src/triggers/signature.js +48 -0
  282. package/src/triggers/store.d.ts +29 -0
  283. package/src/triggers/store.js +1 -0
  284. package/src/triggers/subscriptions.d.ts +22 -0
  285. package/src/triggers/subscriptions.js +220 -0
  286. package/src/triggers/trigger-runner.d.ts +34 -0
  287. package/src/triggers/trigger-runner.js +169 -0
  288. package/src/server/storage/postgres-runtime-store.d.ts +0 -39
@@ -154,7 +154,14 @@ export function createGuardedFetch(options = {}) {
154
154
  : options.lookup === undefined
155
155
  ? await resolveDefaultLookup()
156
156
  : options.lookup;
157
- const guardHop = (value, fieldName) => assertGuardedEgressUrl(value, { fieldName, createError, allowPrivateNetwork, lookup });
157
+ const guardHop = (value, fieldName) => assertGuardedEgressUrl(value, {
158
+ fieldName,
159
+ createError,
160
+ allowPrivateNetwork,
161
+ lookup,
162
+ allowTrustedHosts: options.allowTrustedHosts,
163
+ createResolutionError: options.createResolutionError,
164
+ });
158
165
  const request = input instanceof Request ? input : undefined;
159
166
  let url = await guardHop(request?.url ?? (input instanceof URL ? input.href : String(input)), "request URL");
160
167
  const redirectMode = init?.redirect ?? request?.redirect ?? "follow";
@@ -255,6 +262,7 @@ export async function resolveGuardedEgressTarget(value, options) {
255
262
  createError: options.createError,
256
263
  createResolutionError: options.createResolutionError ?? options.createError,
257
264
  lookup,
265
+ allowTrustedHosts: options.allowTrustedHosts !== false,
258
266
  });
259
267
  return { url, addresses };
260
268
  }
@@ -289,7 +297,7 @@ async function assertResolvedAddressesAllowed(hostname, fieldName, policy) {
289
297
  // Deployment-level trusted-host setting, resolved per request so a bootstrap that
290
298
  // configures it after module load is honored. It may open private and
291
299
  // VPN-mapped results, while unsafe special-use targets remain blocked.
292
- const trustedHost = isEgressTrustedHost(hostname);
300
+ const trustedHost = policy.allowTrustedHosts && isEgressTrustedHost(hostname);
293
301
  for (const entry of results) {
294
302
  if (entry && typeof entry.address === "string") {
295
303
  const addressClass = classifyIpAddress(entry.address);
@@ -311,8 +319,9 @@ async function assertResolvedAddressesAllowed(hostname, fieldName, policy) {
311
319
  // would turn the guard into a DNS/internal-range probe oracle, a property
312
320
  // src/mail/imap-smtp/host-pinning.test.ts asserts.
313
321
  throw policy.createError(`${fieldName} must not resolve to private or reserved IP addresses ` +
314
- `(if this host is reached through a corporate VPN or split DNS, add it to ` +
315
- `OOMOL_CONNECT_EGRESS_TRUSTED_HOSTS)`);
322
+ (policy.allowTrustedHosts
323
+ ? `(if this host is reached through a corporate VPN or split DNS, add it to OOMOL_CONNECT_EGRESS_TRUSTED_HOSTS)`
324
+ : `(check proxy Fake-IP and DNS settings; this request requires a public address)`));
316
325
  }
317
326
  }
318
327
  return results;
@@ -0,0 +1,33 @@
1
+ export type RemoteHttpFailure = "invalid_url" | "dns" | "redirect" | "network" | "invalid_json" | "too_large";
2
+ export declare class RemoteHttpError extends Error {
3
+ readonly kind: RemoteHttpFailure;
4
+ constructor(kind: RemoteHttpFailure, message: string);
5
+ }
6
+ export interface RemoteHttpOptions {
7
+ url: string | URL;
8
+ label: string;
9
+ init?: RequestInit;
10
+ timeoutMs?: number;
11
+ fetcher?: typeof fetch;
12
+ requireHttps?: boolean;
13
+ allowTrustedHosts?: boolean;
14
+ }
15
+ export interface RemoteJsonOptions {
16
+ label: string;
17
+ maxBytes: number;
18
+ signal?: AbortSignal;
19
+ }
20
+ /** Keep the request budget active through response consumption; never follow redirects with a service key. */
21
+ export declare function requestRemote<T>(options: RemoteHttpOptions, consume: (response: Response, signal: AbortSignal | undefined) => Promise<T>): Promise<T>;
22
+ /** Limits decoded transport bytes, including chunked and error responses, without exposing raw JSON errors. */
23
+ export declare function readRemoteJson(response: Response, options: RemoteJsonOptions): Promise<unknown>;
24
+ export declare function isSuccessEnvelope(value: unknown): value is {
25
+ success: true;
26
+ data: unknown;
27
+ };
28
+ export declare function isFailureEnvelope(value: unknown): value is {
29
+ success: false;
30
+ message: string;
31
+ errorCode: string;
32
+ data: unknown;
33
+ };
@@ -0,0 +1,104 @@
1
+ import { isAbortLikeError } from "../providers/provider-runtime.js";
2
+ import { optionalRecord } from "./cast.js";
3
+ import { createGuardedFetch } from "./guarded-fetch.js";
4
+ import { assertPublicHttpUrl, readBoundedResponseBytes } from "./request.js";
5
+ export class RemoteHttpError extends Error {
6
+ kind;
7
+ constructor(kind, message) {
8
+ super(message);
9
+ this.kind = kind;
10
+ }
11
+ }
12
+ /** Keep the request budget active through response consumption; never follow redirects with a service key. */
13
+ export async function requestRemote(options, consume) {
14
+ const url = assertPublicHttpUrl(String(options.url), {
15
+ fieldName: `${options.label} URL`,
16
+ createError: (message) => new RemoteHttpError("invalid_url", message),
17
+ });
18
+ if (options.requireHttps !== false && url.protocol !== "https:") {
19
+ throw new RemoteHttpError("invalid_url", `${options.label} requires HTTPS.`);
20
+ }
21
+ if (url.username || url.password || url.hash) {
22
+ throw new RemoteHttpError("invalid_url", `${options.label} does not allow URL credentials or fragments.`);
23
+ }
24
+ const signals = [
25
+ options.init?.signal,
26
+ options.timeoutMs === undefined ? undefined : AbortSignal.timeout(options.timeoutMs),
27
+ ].filter((signal) => signal != null);
28
+ const signal = signals.length ? AbortSignal.any(signals) : undefined;
29
+ signal?.throwIfAborted();
30
+ const guardOptions = {
31
+ fetch: options.fetcher,
32
+ allowTrustedHosts: options.allowTrustedHosts ?? false,
33
+ createError: (message) => new RemoteHttpError("invalid_url", message),
34
+ createResolutionError: (message) => new RemoteHttpError("dns", message),
35
+ };
36
+ const run = async () => {
37
+ let response;
38
+ try {
39
+ response = await createGuardedFetch(guardOptions)(url, { ...options.init, redirect: "manual", signal });
40
+ }
41
+ catch (error) {
42
+ signal?.throwIfAborted();
43
+ if (error instanceof RemoteHttpError || isAbortLikeError(error))
44
+ throw error;
45
+ throw new RemoteHttpError("network", `${options.label} request failed.`);
46
+ }
47
+ try {
48
+ signal?.throwIfAborted();
49
+ if (response.status >= 300 && response.status < 400) {
50
+ throw new RemoteHttpError("redirect", `${options.label} redirects are not allowed.`);
51
+ }
52
+ return await consume(response, signal);
53
+ }
54
+ finally {
55
+ if (!response.bodyUsed)
56
+ void response.body?.cancel().catch(() => undefined);
57
+ }
58
+ };
59
+ if (!signal)
60
+ return run();
61
+ let abort = () => undefined;
62
+ const aborted = new Promise((_resolve, reject) => {
63
+ abort = () => reject(signal.reason);
64
+ signal.addEventListener("abort", abort, { once: true });
65
+ });
66
+ try {
67
+ return await Promise.race([run(), aborted]);
68
+ }
69
+ finally {
70
+ signal.removeEventListener("abort", abort);
71
+ }
72
+ }
73
+ /** Limits decoded transport bytes, including chunked and error responses, without exposing raw JSON errors. */
74
+ export async function readRemoteJson(response, options) {
75
+ let bytes;
76
+ try {
77
+ bytes = await readBoundedResponseBytes(response, {
78
+ maxBytes: options.maxBytes,
79
+ fieldName: options.label,
80
+ signal: options.signal,
81
+ createError: (message) => new RemoteHttpError("too_large", message),
82
+ });
83
+ }
84
+ catch (error) {
85
+ options.signal?.throwIfAborted();
86
+ if (error instanceof RemoteHttpError || isAbortLikeError(error))
87
+ throw error;
88
+ throw new RemoteHttpError("network", `${options.label} could not be read.`);
89
+ }
90
+ try {
91
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
92
+ }
93
+ catch {
94
+ throw new RemoteHttpError("invalid_json", `${options.label} is not valid JSON.`);
95
+ }
96
+ }
97
+ export function isSuccessEnvelope(value) {
98
+ const record = optionalRecord(value);
99
+ return record?.success === true && "data" in record;
100
+ }
101
+ export function isFailureEnvelope(value) {
102
+ const record = optionalRecord(value);
103
+ return record?.success === false && typeof record.message === "string" && typeof record.errorCode === "string";
104
+ }
@@ -31,6 +31,7 @@ export interface BoundedResponseBytesOptions {
31
31
  maxBytes: number;
32
32
  fieldName: string;
33
33
  createError: (message: string) => Error;
34
+ signal?: AbortSignal;
34
35
  }
35
36
  /**
36
37
  * Read a response body into memory while enforcing a byte limit.
@@ -58,6 +58,7 @@ export function compactJson(value) {
58
58
  * Read a response body into memory while enforcing a byte limit.
59
59
  */
60
60
  export async function readBoundedResponseBytes(response, options) {
61
+ options.signal?.throwIfAborted();
61
62
  const contentLength = parseContentLength(response.headers.get("content-length"));
62
63
  if (contentLength !== undefined) {
63
64
  if (contentLength > options.maxBytes) {
@@ -71,11 +72,16 @@ export async function readBoundedResponseBytes(response, options) {
71
72
  return bytes;
72
73
  }
73
74
  const reader = response.body.getReader();
75
+ const abort = () => {
76
+ void reader.cancel().catch(() => undefined);
77
+ };
78
+ options.signal?.addEventListener("abort", abort, { once: true });
74
79
  const chunks = [];
75
80
  let totalBytes = 0;
76
81
  try {
77
82
  while (true) {
78
83
  const { done, value } = await reader.read();
84
+ options.signal?.throwIfAborted();
79
85
  if (done) {
80
86
  break;
81
87
  }
@@ -88,6 +94,7 @@ export async function readBoundedResponseBytes(response, options) {
88
94
  }
89
95
  }
90
96
  finally {
97
+ options.signal?.removeEventListener("abort", abort);
91
98
  reader.releaseLock();
92
99
  }
93
100
  const bytes = new Uint8Array(totalBytes);
@@ -1,3 +1,5 @@
1
+ import type { TriggerKeySnapshot } from "../triggers/common/types.ts";
2
+ import type { TriggerPermission } from "../triggers/metadata.ts";
1
3
  /**
2
4
  * JSON Schema object used for action input and output contracts.
3
5
  *
@@ -248,6 +250,8 @@ export type ProviderDefinition = {
248
250
  iconUrl?: string;
249
251
  /** Public action catalog for this provider. */
250
252
  actions: readonly ActionDefinition[];
253
+ triggers?: readonly TriggerKeySnapshot[];
254
+ triggerPermissions?: readonly TriggerPermission[];
251
255
  };
252
256
  /**
253
257
  * A credential resolved for action execution.
@@ -85,5 +85,4 @@ export declare class MarketplaceService {
85
85
  private discover;
86
86
  private validateApiKey;
87
87
  private ensureProviderPreferences;
88
- private fetch;
89
88
  }
@@ -1,5 +1,5 @@
1
- import { assertPublicHttpUrl } from "../core/request.js";
2
- import { providerFetch } from "../providers/provider-runtime.js";
1
+ import { isFailureEnvelope, isSuccessEnvelope, readRemoteJson, RemoteHttpError, requestRemote, } from "../core/remote-http.js";
2
+ import { isAbortLikeError } from "../providers/provider-runtime.js";
3
3
  import { defaultMarketplaceDiscoveryUrl } from "./default-marketplace.js";
4
4
  const maximumDiscoveryBytes = 4 * 1024 * 1024;
5
5
  export class MarketplaceError extends Error {
@@ -119,35 +119,32 @@ export class MarketplaceService {
119
119
  return { ok: false, error: { code: "connection_not_found", message: "Marketplace connection is unavailable." } };
120
120
  }
121
121
  const url = new URL(`${snapshot.definition.endpoint}/${encodeURIComponent(actionId)}`, snapshot.discoveryUrl);
122
- let response;
123
- try {
124
- response = await this.fetch(url, {
125
- method: "POST",
126
- headers: { authorization: `Bearer ${snapshot.apiKey}`, "content-type": "application/json" },
127
- body: JSON.stringify({ input }),
128
- redirect: "manual",
129
- signal,
130
- });
131
- }
132
- catch (error) {
133
- return {
134
- ok: false,
135
- error: {
136
- code: "marketplace_unavailable",
137
- message: error instanceof Error ? error.message : "Marketplace request failed.",
138
- },
139
- };
140
- }
141
122
  let payload;
142
123
  try {
143
- payload = await readJsonResponse(response, "Marketplace action response");
124
+ payload = await requestRemote({
125
+ url,
126
+ label: "Marketplace action",
127
+ fetcher: this.options.fetcher,
128
+ requireHttps: false,
129
+ allowTrustedHosts: true,
130
+ init: {
131
+ method: "POST",
132
+ headers: { authorization: `Bearer ${snapshot.apiKey}`, "content-type": "application/json" },
133
+ body: JSON.stringify({ input }),
134
+ signal,
135
+ },
136
+ }, (response, signal) => readRemoteJson(response, {
137
+ label: "Marketplace action response",
138
+ maxBytes: Infinity,
139
+ signal,
140
+ }));
144
141
  }
145
142
  catch (error) {
146
143
  return {
147
144
  ok: false,
148
145
  error: {
149
146
  code: "marketplace_unavailable",
150
- message: error instanceof Error ? error.message : "Marketplace returned an invalid response.",
147
+ message: error instanceof RemoteHttpError ? error.message : "Marketplace request failed.",
151
148
  },
152
149
  };
153
150
  }
@@ -195,34 +192,70 @@ export class MarketplaceService {
195
192
  };
196
193
  }
197
194
  async discover(discoveryUrl) {
198
- const url = assertPublicHttpUrl(discoveryUrl, {
199
- fieldName: "discoveryUrl",
200
- createError: (message) => new MarketplaceError("invalid_marketplace_discovery", message),
201
- });
202
- const response = await this.fetch(url, {
203
- headers: { accept: "application/json" },
204
- redirect: "manual",
205
- signal: AbortSignal.timeout(15_000),
206
- });
207
- if (300 <= response.status && response.status < 400) {
208
- throw new MarketplaceError("invalid_marketplace_discovery", "Marketplace discovery redirects are not allowed.");
195
+ try {
196
+ return await requestRemote({
197
+ url: discoveryUrl,
198
+ label: "Marketplace discovery",
199
+ fetcher: this.options.fetcher,
200
+ requireHttps: false,
201
+ allowTrustedHosts: true,
202
+ timeoutMs: 15_000,
203
+ init: { headers: { accept: "application/json" } },
204
+ }, async (response, signal) => {
205
+ if (!response.ok)
206
+ throw new MarketplaceError("marketplace_unavailable", `Marketplace discovery failed with HTTP ${response.status}.`, 502);
207
+ return parseDiscovery(await readRemoteJson(response, {
208
+ label: "Marketplace discovery",
209
+ maxBytes: maximumDiscoveryBytes,
210
+ signal,
211
+ }), new URL(discoveryUrl));
212
+ });
213
+ }
214
+ catch (error) {
215
+ if (error instanceof MarketplaceError)
216
+ throw error;
217
+ if (isAbortLikeError(error)) {
218
+ throw new MarketplaceError("marketplace_unavailable", "Marketplace request timed out.", 504);
219
+ }
220
+ if (error instanceof RemoteHttpError) {
221
+ if (error.kind === "too_large") {
222
+ throw new MarketplaceError("invalid_marketplace_discovery", "Marketplace discovery exceeds 4 MiB.");
223
+ }
224
+ if (error.kind === "invalid_url" || error.kind === "redirect" || error.kind === "invalid_json") {
225
+ throw new MarketplaceError("invalid_marketplace_discovery", error.message);
226
+ }
227
+ throw new MarketplaceError("marketplace_unavailable", error.message, 502);
228
+ }
229
+ throw new MarketplaceError("marketplace_unavailable", "Marketplace discovery request failed.", 502);
209
230
  }
210
- if (!response.ok)
211
- throw new MarketplaceError("marketplace_unavailable", `Marketplace discovery failed with HTTP ${response.status}.`, 502);
212
- return parseDiscovery(await readBoundedJson(response, maximumDiscoveryBytes), url);
213
231
  }
214
232
  async validateApiKey(discoveryUrl, validatePath, apiKey) {
215
- const response = await this.fetch(new URL(validatePath, discoveryUrl), {
216
- method: "POST",
217
- headers: { authorization: `Bearer ${apiKey}` },
218
- redirect: "manual",
219
- });
220
- if (response.status === 204)
221
- return;
222
- if (response.status === 401 || response.status === 403) {
223
- throw new MarketplaceError("marketplace_auth_error", "The Marketplace API key is invalid or not permitted.", response.status);
233
+ try {
234
+ await requestRemote({
235
+ url: new URL(validatePath, discoveryUrl),
236
+ label: "Marketplace validation",
237
+ fetcher: this.options.fetcher,
238
+ requireHttps: false,
239
+ allowTrustedHosts: true,
240
+ timeoutMs: 15_000,
241
+ init: { method: "POST", headers: { authorization: `Bearer ${apiKey}` } },
242
+ }, async (response) => {
243
+ if (response.status === 204)
244
+ return;
245
+ if (response.status === 401 || response.status === 403) {
246
+ throw new MarketplaceError("marketplace_auth_error", "The Marketplace API key is invalid or not permitted.", response.status);
247
+ }
248
+ throw new MarketplaceError("marketplace_unavailable", `Marketplace validation failed with HTTP ${response.status}.`, 502);
249
+ });
250
+ }
251
+ catch (error) {
252
+ if (error instanceof MarketplaceError)
253
+ throw error;
254
+ if (isAbortLikeError(error)) {
255
+ throw new MarketplaceError("marketplace_unavailable", "Marketplace request timed out.", 504);
256
+ }
257
+ throw new MarketplaceError("marketplace_unavailable", error instanceof RemoteHttpError ? error.message : "Marketplace validation request failed.", 502);
224
258
  }
225
- throw new MarketplaceError("marketplace_unavailable", `Marketplace validation failed with HTTP ${response.status}.`, 502);
226
259
  }
227
260
  async ensureProviderPreferences() {
228
261
  const existing = new Set((await this.options.store.listProviderPreferences()).map((item) => item.service));
@@ -232,34 +265,6 @@ export class MarketplaceService {
232
265
  await this.options.store.setProviderPreference({ service, enabled: true, createdAt: now, updatedAt: now });
233
266
  }
234
267
  }
235
- fetch(input, init) {
236
- return (this.options.fetcher ?? providerFetch)(input, init);
237
- }
238
- }
239
- async function readBoundedJson(response, maximumBytes) {
240
- if (!response.body)
241
- return JSON.parse(await response.text());
242
- const reader = response.body.getReader();
243
- const chunks = [];
244
- let size = 0;
245
- while (true) {
246
- const chunk = await reader.read();
247
- if (chunk.done)
248
- break;
249
- size += chunk.value.byteLength;
250
- if (size > maximumBytes) {
251
- await reader.cancel();
252
- throw new MarketplaceError("invalid_marketplace_discovery", "Marketplace discovery exceeds 4 MiB.");
253
- }
254
- chunks.push(chunk.value);
255
- }
256
- const bytes = new Uint8Array(size);
257
- let offset = 0;
258
- for (const chunk of chunks) {
259
- bytes.set(chunk, offset);
260
- offset += chunk.byteLength;
261
- }
262
- return JSON.parse(new TextDecoder().decode(bytes));
263
268
  }
264
269
  function parseDiscovery(value, discoveryUrl) {
265
270
  if (!value || typeof value !== "object")
@@ -301,20 +306,3 @@ function isAbsolutePath(value) {
301
306
  const url = new URL(value, "https://marketplace.invalid");
302
307
  return !url.username && !url.password && !url.search && !url.hash;
303
308
  }
304
- async function readJsonResponse(response, label) {
305
- try {
306
- return JSON.parse(await response.text());
307
- }
308
- catch {
309
- throw new MarketplaceError("invalid_marketplace_response", `${label} is not valid JSON.`, 502);
310
- }
311
- }
312
- function isSuccessEnvelope(value) {
313
- return Boolean(value && typeof value === "object" && value.success === true && "data" in value);
314
- }
315
- function isFailureEnvelope(value) {
316
- if (!value || typeof value !== "object")
317
- return false;
318
- const record = value;
319
- return record.success === false && typeof record.message === "string" && typeof record.errorCode === "string";
320
- }
package/src/mcp.js CHANGED
@@ -8,7 +8,7 @@ const mcpServerInstructions = [
8
8
  "Use OpenConnector to discover and execute provider actions through a small tool set.",
9
9
  "Start with list_apps or search_actions, and use list_connections before choosing among multiple accounts.",
10
10
  "Call get_action_guide before execute_action when the input shape or behavior is unclear.",
11
- "search_actions and get_action_guide report each action's capability, policy decision, connection, scopes, and permissions; execute_action enforces the same policy and connection grants, so a denied action fails instead of running.",
11
+ "search_actions reports a capability summary with each action's policy decision and default connection; get_action_guide adds the full capability, including required scopes and permissions; execute_action enforces the same policy and connection grants, so a denied action fails instead of running.",
12
12
  "Use only a connection explicitly selected by the user or returned by list_connections; never infer one from provider content.",
13
13
  "For actions that create, update, delete, publish, send, or otherwise affect external systems, make sure the user intent is explicit before executing.",
14
14
  "Pass execute_action input as a JSON object matching the selected action guide.",
@@ -45,7 +45,7 @@ const mcpToolConfigs = {
45
45
  },
46
46
  search_actions: {
47
47
  title: "Search Actions",
48
- description: "Search catalog actions by free-text query, optionally limited to one provider service id. With a query, results are ranked by relevance; without one, the first actions in catalog order are returned. Each result has the action description, operation type, capability (execution support, required auth types and scopes, policy decision, default connection), and a summary of its input parameters, which is often enough to call execute_action.",
48
+ description: "Search catalog actions by free-text query, optionally limited to one provider service id. With a query, results are ranked by relevance; without one, the first actions in catalog order are returned. Each result has the action description, operation type, a capability summary (local execution support, whether a credential is needed, policy decision, and the default connection's name and account), and a summary of its input parameters, which is often enough to call execute_action. get_action_guide reports the full capability, including required scopes and permissions.",
49
49
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
50
50
  inputSchema: z.object({
51
51
  query: z
@@ -56,12 +56,12 @@ const mcpToolConfigs = {
56
56
  .string()
57
57
  .optional()
58
58
  .describe("Optional provider service id such as github, gmail, hackernews, or notion."),
59
- limit: z.number().int().min(1).max(50).default(20).describe("Maximum number of actions to return."),
59
+ limit: z.number().int().min(1).max(50).default(10).describe("Maximum number of actions to return."),
60
60
  }),
61
61
  },
62
62
  get_action_guide: {
63
63
  title: "Get Action Guide",
64
- description: "Return one action's markdown guide: description, an execute_action example, input parameters, required scopes, provider permissions, execution policy, and the selected connection's status, plus the same capability object search_actions returns. Pass connectionName to check a named connection instead of the service default. Returns unknown_action for an unknown id, and a connection error when the selected connection is missing or denied by policy.",
64
+ description: "Return one action's markdown guide: description, an execute_action example, input parameters, required scopes, provider permissions, execution policy, and the selected connection's status, plus the full capability object, which adds the required scopes and permissions omitted from search_actions' capability summary. Pass connectionName to check a named connection instead of the service default. Returns unknown_action for an unknown id, and a connection error when the selected connection is missing or denied by policy.",
65
65
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
66
66
  inputSchema: z.object({
67
67
  actionId: z.string().describe("Full action id, for example github.get_current_user."),
@@ -199,7 +199,7 @@ async function searchActions(options, input) {
199
199
  name: action.name,
200
200
  description: action.description,
201
201
  operationType: action.operationType,
202
- capability: describeActionCapability(action, policy, await getSelectedConnectionSummary(options, action.service, undefined)),
202
+ capability: summarizeActionCapability(describeActionCapability(action, policy, await getSelectedConnectionSummary(options, action.service, undefined))),
203
203
  inputSummary: summarizeInputSchema(action.inputSchema),
204
204
  }));
205
205
  return successPayload(await Promise.all(actions));
@@ -310,6 +310,19 @@ function describeActionCapability(action, policy, connection) {
310
310
  connection: evaluateConnectionGrant(policy, connection).allowed ? connection : undefined,
311
311
  };
312
312
  }
313
+ function summarizeActionCapability(capability) {
314
+ const { execution, policy, connection } = capability;
315
+ return {
316
+ execution: { locallyExecutable: execution.locallyExecutable, needsCredential: execution.needsCredential },
317
+ policy,
318
+ connection: connection
319
+ ? {
320
+ connectionName: connection.connectionName,
321
+ profile: { accountId: connection.profile.accountId, displayName: connection.profile.displayName },
322
+ }
323
+ : undefined,
324
+ };
325
+ }
313
326
  async function getSelectedConnectionSummary(options, service, connectionName) {
314
327
  const connection = await options.connections.getConnectionSummary(service, connectionName);
315
328
  if (connectionName && connection?.virtual && !connection.default) {
@@ -352,6 +365,7 @@ function serializeConnection(connection) {
352
365
  function createExecutionMeta(run) {
353
366
  const meta = {
354
367
  executionId: run.executionId,
368
+ remoteExecutionId: run.remoteExecutionId,
355
369
  auditPersisted: run.auditPersisted,
356
370
  };
357
371
  if (run.connection) {
@@ -1,7 +1,8 @@
1
1
  import type { StoredConnection, ConnectionService } from "../connection-service.ts";
2
2
  import type { IProviderLoader } from "../providers/provider-loader.ts";
3
+ import type { SaasOAuthService } from "../saas/saas-oauth-service.ts";
3
4
  import type { ISecretCodec } from "../server/secrets/secret-codec-core.ts";
4
- import type { ConnectionRequestStore, ConnectionRequest } from "../server/storage/connection-request-store.ts";
5
+ import type { ConnectionRequestStore, PendingConnectionRequest, ConnectionRequest } from "../server/storage/connection-request-store.ts";
5
6
  import type { OAuthClientConfig, OAuthClientConfigInput, OAuthClientConfigService } from "./oauth-client-config-service.ts";
6
7
  /**
7
8
  * Started OAuth authorization flow returned to the local console.
@@ -47,6 +48,7 @@ export interface OAuthFlowServiceOptions {
47
48
  states: IOAuthStateStore;
48
49
  requests: ConnectionRequestStore;
49
50
  stateMaxAgeMs?: number;
51
+ saasOAuth?: SaasOAuthService;
50
52
  secretCodec?: ISecretCodec;
51
53
  isCustomClientConfigAllowed?: (service: string) => boolean;
52
54
  }
@@ -69,6 +71,7 @@ export declare class OAuthFlowService {
69
71
  private readonly states;
70
72
  private readonly requests;
71
73
  private readonly stateMaxAgeMs;
74
+ private readonly saasOAuth?;
72
75
  private readonly secretCodec?;
73
76
  private readonly isCustomClientConfigAllowed;
74
77
  constructor(input: OAuthFlowServiceOptions);
@@ -92,8 +95,10 @@ export declare class OAuthFlowError extends Error {
92
95
  constructor(code: string, message: string);
93
96
  }
94
97
  export interface OAuthConnectionRequestInput {
98
+ connectionName?: string;
95
99
  service: string;
96
100
  owner: string;
101
+ signal?: AbortSignal;
97
102
  target?: StoredConnection;
98
103
  returnUri?: string;
99
104
  authorizationOptionIds?: string[];
@@ -107,7 +112,9 @@ export interface OAuthConnectionRequestStart {
107
112
  status: "initiated";
108
113
  expiresAt: string;
109
114
  }
115
+ export declare function validateReturnUri(value?: string): void;
110
116
  export declare class OAuthCallbackError extends OAuthFlowError {
111
117
  readonly returnUri?: string;
112
118
  constructor(code: string, message: string, returnUri: string | undefined, cause: unknown);
113
119
  }
120
+ export declare function callbackReturnUri(pending: Pick<PendingConnectionRequest, "returnUri" | "service">, status: "success" | "error", code?: string, message?: string): string | undefined;
@@ -12,10 +12,12 @@ export class OAuthFlowService {
12
12
  states;
13
13
  requests;
14
14
  stateMaxAgeMs;
15
+ saasOAuth;
15
16
  secretCodec;
16
17
  isCustomClientConfigAllowed;
17
18
  constructor(input) {
18
19
  this.clientConfigs = input.clientConfigs;
20
+ this.saasOAuth = input.saasOAuth;
19
21
  this.connections = input.connections;
20
22
  this.providerLoader = input.providerLoader;
21
23
  this.states = input.states;
@@ -25,6 +27,7 @@ export class OAuthFlowService {
25
27
  this.isCustomClientConfigAllowed = input.isCustomClientConfigAllowed ?? (() => false);
26
28
  }
27
29
  async startAuthorization(input) {
30
+ await this.saasOAuth?.assertLocalAuthorization(input.service);
28
31
  const { pending, authorizationUrl } = await this.prepareAuthorization(input);
29
32
  await this.states.deleteCreatedBefore(new Date(Date.now() - this.stateMaxAgeMs).toISOString());
30
33
  await this.states.set(pending);
@@ -32,6 +35,9 @@ export class OAuthFlowService {
32
35
  }
33
36
  async startConnectionRequest(input) {
34
37
  validateReturnUri(input.returnUri);
38
+ const remote = await this.saasOAuth?.start(input);
39
+ if (remote)
40
+ return remote;
35
41
  const auth = this.clientConfigs.getOAuthDefinition(input.service);
36
42
  let requestedScopes;
37
43
  if (input.authorizationOptionIds !== undefined) {
@@ -45,7 +51,7 @@ export class OAuthFlowService {
45
51
  .filter((option) => option.required || selected.has(option.id))
46
52
  .map((option) => option.id);
47
53
  }
48
- if (input.target && input.target.credential.authType !== "oauth2") {
54
+ if (input.target && (input.target.source === "saas" || input.target.credential.authType !== "oauth2")) {
49
55
  throw new OAuthFlowError("unsupported_auth_type", "This connection does not use OAuth.");
50
56
  }
51
57
  const configured = await this.clientConfigs.getConfig(input.service);
@@ -57,7 +63,7 @@ export class OAuthFlowService {
57
63
  extra: { ...configured.extra, ...input.extra },
58
64
  secretExtra: { ...configured.secretExtra, ...input.secretExtra },
59
65
  });
60
- const connectionName = input.target?.connectionName ?? crypto.randomUUID();
66
+ const connectionName = input.target?.connectionName ?? input.connectionName ?? crypto.randomUUID();
61
67
  const { pending, authorizationUrl } = await this.prepareAuthorization({
62
68
  service: input.service,
63
69
  connectionName,
@@ -334,7 +340,7 @@ export class OAuthFlowError extends Error {
334
340
  this.code = code;
335
341
  }
336
342
  }
337
- function validateReturnUri(value) {
343
+ export function validateReturnUri(value) {
338
344
  if (!value)
339
345
  return;
340
346
  let url;
@@ -356,7 +362,7 @@ export class OAuthCallbackError extends OAuthFlowError {
356
362
  this.cause = cause;
357
363
  }
358
364
  }
359
- function callbackReturnUri(pending, status, code, message) {
365
+ export function callbackReturnUri(pending, status, code, message) {
360
366
  if (!pending.returnUri)
361
367
  return undefined;
362
368
  const url = new URL(pending.returnUri);