@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.
- package/README.md +4 -0
- package/assets/open-connector/catalog/apps/airtable.json +134 -0
- package/assets/open-connector/catalog/apps/aisa.json +916 -76
- package/assets/open-connector/catalog/apps/cal.json +24 -6
- package/assets/open-connector/catalog/apps/discordbot.json +2 -2
- package/assets/open-connector/catalog/apps/enormail.json +411 -0
- package/assets/open-connector/catalog/apps/feishu_app_bot.json +158 -0
- package/assets/open-connector/catalog/apps/github.json +275 -0
- package/assets/open-connector/catalog/apps/gitlab.json +125 -0
- package/assets/open-connector/catalog/apps/gmail.json +151 -0
- package/assets/open-connector/catalog/apps/googlecalendar.json +206 -0
- package/assets/open-connector/catalog/apps/googledrive.json +479 -0
- package/assets/open-connector/catalog/apps/googlesheets.json +144 -0
- package/assets/open-connector/catalog/apps/hithink_finance.json +6 -3
- package/assets/open-connector/catalog/apps/indexed.json +1115 -0
- package/assets/open-connector/catalog/apps/linear.json +116 -0
- package/assets/open-connector/catalog/apps/notion.json +142 -0
- package/assets/open-connector/catalog/apps/one_drive.json +185 -0
- package/assets/open-connector/catalog/apps/outlook.json +182 -0
- package/assets/open-connector/catalog/apps/shopify.json +282 -0
- package/assets/open-connector/catalog/apps/slack.json +182 -0
- package/assets/open-connector/catalog/apps/spotify.json +46 -43
- package/assets/open-connector/catalog/apps/stripe.json +96 -0
- package/assets/open-connector/catalog/apps/telegram.json +138 -0
- package/assets/open-connector/catalog/apps/weixin_store.json +543 -2
- package/assets/open-connector/catalog/apps/woocommerce.json +127 -0
- package/assets/open-connector/catalog/apps/zendesk.json +152 -0
- package/assets/open-connector/catalog/apps/zerotier.json +9 -9
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/assets/open-connector/migrations/0014_saas_project.sql +55 -0
- package/assets/open-connector/migrations/0015_saas_cleanup_runtime.sql +8 -0
- package/assets/open-connector/migrations/0016_trigger_policy.sql +1 -0
- package/assets/open-connector/migrations/0017_trigger_subscriptions.sql +17 -0
- package/assets/open-connector/migrations/postgresql/0014_saas_project.sql +55 -0
- package/assets/open-connector/migrations/postgresql/0015_saas_cleanup_runtime.sql +8 -0
- package/assets/open-connector/migrations/postgresql/0016_trigger_policy.sql +1 -0
- package/assets/open-connector/migrations/postgresql/0017_trigger_subscriptions.sql +17 -0
- package/package.json +2 -2
- package/src/connection-service.d.ts +40 -7
- package/src/connection-service.js +60 -18
- package/src/core/action-policy.d.ts +8 -1
- package/src/core/action-policy.js +51 -0
- package/src/core/cast.d.ts +3 -0
- package/src/core/cast.js +4 -1
- package/src/core/guarded-fetch.d.ts +5 -0
- package/src/core/guarded-fetch.js +13 -4
- package/src/core/remote-http.d.ts +33 -0
- package/src/core/remote-http.js +104 -0
- package/src/core/request.d.ts +1 -0
- package/src/core/request.js +7 -0
- package/src/core/types.d.ts +4 -0
- package/src/marketplace/marketplace-service.d.ts +0 -1
- package/src/marketplace/marketplace-service.js +80 -92
- package/src/mcp.js +19 -5
- package/src/oauth/oauth-flow-service.d.ts +8 -1
- package/src/oauth/oauth-flow-service.js +10 -4
- package/src/providers/action-contracts.generated.d.ts +4 -2
- package/src/providers/airtable/executors.d.ts +3 -0
- package/src/providers/airtable/executors.js +2 -0
- package/src/providers/airtable/trigger-on-record-changed.d.ts +2 -0
- package/src/providers/airtable/trigger-on-record-changed.definition.d.ts +4 -0
- package/src/providers/airtable/trigger-on-record-changed.definition.js +54 -0
- package/src/providers/airtable/trigger-on-record-changed.js +235 -0
- package/src/providers/aisa/executors.js +196 -22
- package/src/providers/cal/actions.js +5 -1
- package/src/providers/enormail/executors.d.ts +4 -0
- package/src/providers/enormail/executors.js +97 -0
- package/src/providers/feishu_app_bot/executors.d.ts +3 -0
- package/src/providers/feishu_app_bot/executors.js +2 -0
- package/src/providers/feishu_app_bot/trigger-config.d.ts +1 -0
- package/src/providers/feishu_app_bot/trigger-config.js +10 -0
- package/src/providers/feishu_app_bot/trigger-on-event.d.ts +2 -0
- package/src/providers/feishu_app_bot/trigger-on-event.definition.d.ts +4 -0
- package/src/providers/feishu_app_bot/trigger-on-event.definition.js +63 -0
- package/src/providers/feishu_app_bot/trigger-on-event.js +29 -0
- package/src/providers/feishu_app_bot/trigger-subscriptions.d.ts +5 -0
- package/src/providers/feishu_app_bot/trigger-subscriptions.js +70 -0
- package/src/providers/github/executors.d.ts +3 -0
- package/src/providers/github/executors.js +6 -0
- package/src/providers/github/trigger-on-repo-event.d.ts +2 -0
- package/src/providers/github/trigger-on-repo-event.definition.d.ts +4 -0
- package/src/providers/github/trigger-on-repo-event.definition.js +94 -0
- package/src/providers/github/trigger-on-repo-event.js +168 -0
- package/src/providers/github/trigger-watch-pull-request.d.ts +2 -0
- package/src/providers/github/trigger-watch-pull-request.definition.d.ts +4 -0
- package/src/providers/github/trigger-watch-pull-request.definition.js +49 -0
- package/src/providers/github/trigger-watch-pull-request.js +106 -0
- package/src/providers/gitlab/executors.d.ts +3 -0
- package/src/providers/gitlab/executors.js +2 -0
- package/src/providers/gitlab/trigger-on-project-event.d.ts +2 -0
- package/src/providers/gitlab/trigger-on-project-event.definition.d.ts +5 -0
- package/src/providers/gitlab/trigger-on-project-event.definition.js +63 -0
- package/src/providers/gitlab/trigger-on-project-event.js +186 -0
- package/src/providers/gmail/executors.d.ts +3 -0
- package/src/providers/gmail/executors.js +2 -0
- package/src/providers/gmail/trigger-on-message-received.d.ts +2 -0
- package/src/providers/gmail/trigger-on-message-received.definition.d.ts +4 -0
- package/src/providers/gmail/trigger-on-message-received.definition.js +74 -0
- package/src/providers/gmail/trigger-on-message-received.js +232 -0
- package/src/providers/googlecalendar/executors.d.ts +3 -0
- package/src/providers/googlecalendar/executors.js +2 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.d.ts +2 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.definition.d.ts +4 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.definition.js +74 -0
- package/src/providers/googlecalendar/trigger-on-event-changed.js +233 -0
- package/src/providers/googledrive/executors.d.ts +3 -0
- package/src/providers/googledrive/executors.js +7 -0
- package/src/providers/googledrive/trigger-changes.d.ts +12 -0
- package/src/providers/googledrive/trigger-changes.definition.d.ts +7 -0
- package/src/providers/googledrive/trigger-changes.definition.js +101 -0
- package/src/providers/googledrive/trigger-changes.js +452 -0
- package/src/providers/googledrive/trigger-on-file-change.d.ts +2 -0
- package/src/providers/googledrive/trigger-on-file-change.definition.d.ts +4 -0
- package/src/providers/googledrive/trigger-on-file-change.definition.js +77 -0
- package/src/providers/googledrive/trigger-on-file-change.js +258 -0
- package/src/providers/googlesheets/executors.d.ts +3 -0
- package/src/providers/googlesheets/executors.js +2 -0
- package/src/providers/googlesheets/trigger-on-row-added.d.ts +2 -0
- package/src/providers/googlesheets/trigger-on-row-added.definition.d.ts +4 -0
- package/src/providers/googlesheets/trigger-on-row-added.definition.js +54 -0
- package/src/providers/googlesheets/trigger-on-row-added.js +234 -0
- package/src/providers/hithink_finance/runtime.js +1 -1
- package/src/providers/indexed/executors.d.ts +4 -0
- package/src/providers/indexed/executors.js +16 -0
- package/src/providers/indexed/runtime.d.ts +26 -0
- package/src/providers/indexed/runtime.js +129 -0
- package/src/providers/linear/executors.d.ts +3 -0
- package/src/providers/linear/executors.js +2 -0
- package/src/providers/linear/trigger-on-issue-changed.d.ts +2 -0
- package/src/providers/linear/trigger-on-issue-changed.definition.d.ts +5 -0
- package/src/providers/linear/trigger-on-issue-changed.definition.js +43 -0
- package/src/providers/linear/trigger-on-issue-changed.js +215 -0
- package/src/providers/notion/executors.d.ts +3 -0
- package/src/providers/notion/executors.js +2 -0
- package/src/providers/notion/trigger-on-database-page-event.d.ts +2 -0
- package/src/providers/notion/trigger-on-database-page-event.definition.d.ts +4 -0
- package/src/providers/notion/trigger-on-database-page-event.definition.js +63 -0
- package/src/providers/notion/trigger-on-database-page-event.js +293 -0
- package/src/providers/one_drive/executors.d.ts +3 -0
- package/src/providers/one_drive/executors.js +2 -0
- package/src/providers/one_drive/trigger-on-item-changed.d.ts +2 -0
- package/src/providers/one_drive/trigger-on-item-changed.definition.d.ts +4 -0
- package/src/providers/one_drive/trigger-on-item-changed.definition.js +63 -0
- package/src/providers/one_drive/trigger-on-item-changed.js +214 -0
- package/src/providers/outlook/executors.d.ts +3 -0
- package/src/providers/outlook/executors.js +2 -0
- package/src/providers/outlook/trigger-on-message-received.d.ts +2 -0
- package/src/providers/outlook/trigger-on-message-received.definition.d.ts +4 -0
- package/src/providers/outlook/trigger-on-message-received.definition.js +93 -0
- package/src/providers/outlook/trigger-on-message-received.js +226 -0
- package/src/providers/outlook_calendar/executors.js +6 -0
- package/src/providers/provider-loader.d.ts +5 -0
- package/src/providers/provider-loader.js +4 -0
- package/src/providers/registry.generated.js +2 -0
- package/src/providers/shopify/executors.d.ts +3 -0
- package/src/providers/shopify/executors.js +2 -0
- package/src/providers/shopify/trigger-on-shop-event.d.ts +2 -0
- package/src/providers/shopify/trigger-on-shop-event.definition.d.ts +4 -0
- package/src/providers/shopify/trigger-on-shop-event.definition.js +214 -0
- package/src/providers/shopify/trigger-on-shop-event.js +165 -0
- package/src/providers/slack/executors.d.ts +3 -0
- package/src/providers/slack/executors.js +2 -0
- package/src/providers/slack/trigger-on-message-posted.d.ts +2 -0
- package/src/providers/slack/trigger-on-message-posted.definition.d.ts +4 -0
- package/src/providers/slack/trigger-on-message-posted.definition.js +80 -0
- package/src/providers/slack/trigger-on-message-posted.js +176 -0
- package/src/providers/spotify/runtime.js +15 -4
- package/src/providers/stripe/executors.d.ts +3 -0
- package/src/providers/stripe/executors.js +2 -0
- package/src/providers/stripe/trigger-on-event.d.ts +2 -0
- package/src/providers/stripe/trigger-on-event.definition.d.ts +4 -0
- package/src/providers/stripe/trigger-on-event.definition.js +43 -0
- package/src/providers/stripe/trigger-on-event.js +212 -0
- package/src/providers/telegram/executors.d.ts +3 -0
- package/src/providers/telegram/executors.js +2 -0
- package/src/providers/telegram/trigger-on-update.d.ts +2 -0
- package/src/providers/telegram/trigger-on-update.definition.d.ts +5 -0
- package/src/providers/telegram/trigger-on-update.definition.js +78 -0
- package/src/providers/telegram/trigger-on-update.js +141 -0
- package/src/providers/weixin_store/access-token.d.ts +2 -1
- package/src/providers/weixin_store/access-token.js +15 -4
- package/src/providers/weixin_store/executors.js +97 -12
- package/src/providers/woocommerce/executors.d.ts +3 -0
- package/src/providers/woocommerce/executors.js +2 -0
- package/src/providers/woocommerce/trigger-on-store-event.d.ts +2 -0
- package/src/providers/woocommerce/trigger-on-store-event.definition.d.ts +4 -0
- package/src/providers/woocommerce/trigger-on-store-event.definition.js +55 -0
- package/src/providers/woocommerce/trigger-on-store-event.js +179 -0
- package/src/providers/zendesk/executors.d.ts +3 -0
- package/src/providers/zendesk/executors.js +2 -0
- package/src/providers/zendesk/trigger-on-event.d.ts +2 -0
- package/src/providers/zendesk/trigger-on-event.definition.d.ts +5 -0
- package/src/providers/zendesk/trigger-on-event.definition.js +101 -0
- package/src/providers/zendesk/trigger-on-event.js +188 -0
- package/src/saas/saas-cleanup-service.d.ts +26 -0
- package/src/saas/saas-cleanup-service.js +124 -0
- package/src/saas/saas-client.d.ts +86 -0
- package/src/saas/saas-client.js +369 -0
- package/src/saas/saas-execution-service.d.ts +15 -0
- package/src/saas/saas-execution-service.js +21 -0
- package/src/saas/saas-oauth-service.d.ts +25 -0
- package/src/saas/saas-oauth-service.js +197 -0
- package/src/saas/saas-project-service.d.ts +61 -0
- package/src/saas/saas-project-service.js +211 -0
- package/src/server/actions/action-idempotency.d.ts +1 -0
- package/src/server/actions/action-idempotency.js +1 -0
- package/src/server/actions/action-runner.d.ts +7 -0
- package/src/server/actions/action-runner.js +43 -7
- package/src/server/actions/run-log-summary.js +9 -0
- package/src/server/api/auth.d.ts +1 -0
- package/src/server/api/auth.js +13 -2
- package/src/server/api/cache-policy.js +4 -1
- package/src/server/api/connection-input.d.ts +7 -1
- package/src/server/api/connection-input.js +7 -1
- package/src/server/api/connection-routes.d.ts +3 -1
- package/src/server/api/connection-routes.js +21 -3
- package/src/server/api/http-utils.d.ts +4 -3
- package/src/server/api/oauth-completion-page.d.ts +124 -0
- package/src/server/api/oauth-completion-page.js +113 -18
- package/src/server/api/openapi.js +112 -14
- package/src/server/api/policy-input.js +6 -0
- package/src/server/api/runtime-api.d.ts +10 -2
- package/src/server/api/runtime-api.js +17 -4
- package/src/server/api/saas-completion-page.d.ts +2 -0
- package/src/server/api/saas-completion-page.js +103 -0
- package/src/server/connect-app.d.ts +5 -0
- package/src/server/connect-app.js +50 -0
- package/src/server/connect-server.d.ts +12 -0
- package/src/server/connect-server.js +147 -13
- package/src/server/connector-runtime.d.ts +3 -1
- package/src/server/connector-runtime.js +5 -2
- package/src/server/files/transit-files.js +16 -7
- package/src/server/proxy/proxy-runner.d.ts +5 -1
- package/src/server/proxy/proxy-runner.js +72 -26
- package/src/server/storage/connection-request-store.d.ts +41 -1
- package/src/server/storage/connection-request-store.js +245 -20
- package/src/server/storage/connection-store.d.ts +16 -0
- package/src/server/storage/connection-store.js +119 -0
- package/src/server/storage/node-runtime-database.js +3 -3
- package/src/server/storage/{postgres-migrations.d.ts → postgres/migrations.d.ts} +2 -2
- package/src/server/storage/{postgres-migrations.js → postgres/migrations.js} +1 -1
- package/src/server/storage/postgres/runtime-store.d.ts +43 -0
- package/src/server/storage/{postgres-runtime-store.js → postgres/runtime-store.js} +59 -94
- package/src/server/storage/runtime-database.d.ts +4 -0
- package/src/server/storage/runtime-sql.d.ts +1 -1
- package/src/server/storage/runtime-sql.js +2 -1
- package/src/server/storage/runtime-store.d.ts +1 -0
- package/src/server/storage/runtime-token-service.d.ts +2 -0
- package/src/server/storage/runtime-token-service.js +3 -0
- package/src/server/storage/saas-project-store.d.ts +77 -0
- package/src/server/storage/saas-project-store.js +273 -0
- package/src/server/storage/{sqlite-runtime-store.d.ts → sqlite/runtime-store.d.ts} +21 -25
- package/src/server/storage/{sqlite-runtime-store.js → sqlite/runtime-store.js} +100 -115
- package/src/server/storage/trigger-store.d.ts +16 -0
- package/src/server/storage/trigger-store.js +96 -0
- package/src/triggers/common/config.d.ts +2 -0
- package/src/triggers/common/config.js +17 -0
- package/src/triggers/common/configOptions.d.ts +13 -0
- package/src/triggers/common/configOptions.js +1 -0
- package/src/triggers/common/encoding.d.ts +3 -0
- package/src/triggers/common/encoding.js +19 -0
- package/src/triggers/common/integration.d.ts +115 -0
- package/src/triggers/common/integration.js +31 -0
- package/src/triggers/common/json.d.ts +6 -0
- package/src/triggers/common/json.js +91 -0
- package/src/triggers/common/poll.d.ts +41 -0
- package/src/triggers/common/poll.js +15 -0
- package/src/triggers/common/proxy.d.ts +14 -0
- package/src/triggers/common/proxy.js +1 -0
- package/src/triggers/common/types.d.ts +42 -0
- package/src/triggers/common/types.js +1 -0
- package/src/triggers/maintenance.d.ts +40 -0
- package/src/triggers/maintenance.js +120 -0
- package/src/triggers/metadata.d.ts +8 -0
- package/src/triggers/metadata.js +1 -0
- package/src/triggers/request.d.ts +33 -0
- package/src/triggers/request.js +52 -0
- package/src/triggers/resources.d.ts +12 -0
- package/src/triggers/resources.js +121 -0
- package/src/triggers/signature.d.ts +4 -0
- package/src/triggers/signature.js +48 -0
- package/src/triggers/store.d.ts +29 -0
- package/src/triggers/store.js +1 -0
- package/src/triggers/subscriptions.d.ts +22 -0
- package/src/triggers/subscriptions.js +220 -0
- package/src/triggers/trigger-runner.d.ts +34 -0
- package/src/triggers/trigger-runner.js +169 -0
- 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, {
|
|
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
|
-
|
|
315
|
-
|
|
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
|
+
}
|
package/src/core/request.d.ts
CHANGED
package/src/core/request.js
CHANGED
|
@@ -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);
|
package/src/core/types.d.ts
CHANGED
|
@@ -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.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
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,
|
|
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(
|
|
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
|
|
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);
|