@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
|
@@ -8,6 +8,7 @@ import { PromiseCache } from "../core/promise-cache.js";
|
|
|
8
8
|
import { MarketplaceError } from "../marketplace/marketplace-service.js";
|
|
9
9
|
import { OAuthClientConfigError, OAuthClientConfigService } from "../oauth/oauth-client-config-service.js";
|
|
10
10
|
import { OAuthCallbackError, OAuthFlowError, OAuthFlowService } from "../oauth/oauth-flow-service.js";
|
|
11
|
+
import { SaasError } from "../saas/saas-client.js";
|
|
11
12
|
import { ActionInputDepthError, createIdempotencyExpiry, hashActionRequest, hashIdempotencyKey, readIdempotencyKey, } from "./actions/action-idempotency.js";
|
|
12
13
|
import { ActionRunner } from "./actions/action-runner.js";
|
|
13
14
|
import { renderActionMarkdown } from "./api/action-markdown.js";
|
|
@@ -17,7 +18,9 @@ import { createConnectionRoutes } from "./api/connection-routes.js";
|
|
|
17
18
|
import { HttpRequestError, internalError, jsonError, notFound, readJsonBody } from "./api/http-utils.js";
|
|
18
19
|
import { renderOAuthCompletionPage } from "./api/oauth-completion-page.js";
|
|
19
20
|
import { policyRequestMaxBytes, readRuntimePolicyRules, readTokenPolicy } from "./api/policy-input.js";
|
|
21
|
+
import { serializeRuntimeTriggerPermissions } from "./api/runtime-api.js";
|
|
20
22
|
import { mapConnectionErrorStatus, serializeRuntimeAction, serializeRuntimeActionResult, serializeRuntimeActionService, serializeRuntimeConnectedApp, serializeRuntimeFailure, serializeRuntimeProvider, serializeRuntimeProviderSetup, unknownActionFailure, unknownServiceFailure, writeRuntimeActionHttpResult, writeRuntimeFailure, writeRuntimeSuccess, } from "./api/runtime-api.js";
|
|
23
|
+
import { renderSaasCompletionPage } from "./api/saas-completion-page.js";
|
|
21
24
|
import { TransitFileError } from "./files/transit-file-store.js";
|
|
22
25
|
import { ProxyRunner } from "./proxy/proxy-runner.js";
|
|
23
26
|
import { decodeRunLogCursor } from "./storage/runtime-store.js";
|
|
@@ -78,6 +81,7 @@ export class ConnectServer {
|
|
|
78
81
|
providerLoader: options.providerLoader,
|
|
79
82
|
connections: options.connections,
|
|
80
83
|
logger: options.logger,
|
|
84
|
+
saas: options.saas,
|
|
81
85
|
});
|
|
82
86
|
}
|
|
83
87
|
createApp() {
|
|
@@ -115,6 +119,21 @@ export class ConnectServer {
|
|
|
115
119
|
}
|
|
116
120
|
app.get("/v1/health", (context) => writeRuntimeSuccess(context, { ok: true, runtime: "oomol-connect" }));
|
|
117
121
|
app.get("/v1/providers/:service/setup", (context) => this.getRuntimeProviderSetup(context, context.req.param("service")));
|
|
122
|
+
app.get("/v1/providers/:service/trigger-permissions", (context) => this.getRuntimeTriggerPermissions(context, context.req.param("service")));
|
|
123
|
+
app.post("/v1/providers/:service/triggers/:triggerId/execute", (context) => this.executeRuntimeTrigger(context));
|
|
124
|
+
app.get("/api/trigger-subscriptions", async (context) => context.json((await this.options.triggerMaintenance?.list()) ?? []));
|
|
125
|
+
app.post("/api/trigger-subscriptions/:id/cancel", async (context) => {
|
|
126
|
+
if (!this.options.triggerMaintenance)
|
|
127
|
+
throw new HttpRequestError("trigger_not_supported", "Trigger subscriptions are unavailable.", 501);
|
|
128
|
+
await this.options.triggerMaintenance.cancel(context.req.param("id"), context.req.raw.signal);
|
|
129
|
+
return context.json({ ok: true });
|
|
130
|
+
});
|
|
131
|
+
app.post("/api/trigger-subscriptions/:id/abandon", async (context) => {
|
|
132
|
+
if (!this.options.triggerMaintenance)
|
|
133
|
+
throw new HttpRequestError("trigger_not_supported", "Trigger subscriptions are unavailable.", 501);
|
|
134
|
+
await this.options.triggerMaintenance.abandon(context.req.param("id"), context.req.raw.signal);
|
|
135
|
+
return context.json({ ok: true });
|
|
136
|
+
});
|
|
118
137
|
app.get("/v1/providers", (context) => this.listRuntimeProviders(context));
|
|
119
138
|
app.get("/v1/actions", (context) => this.listRuntimeActions(context));
|
|
120
139
|
app.get("/v1/actions/search", (context) => this.searchRuntimeActions(context));
|
|
@@ -166,9 +185,66 @@ export class ConnectServer {
|
|
|
166
185
|
app.delete("/api/runtime-tokens/:id", (context) => this.revokeRuntimeToken(context, context.req.param("id")));
|
|
167
186
|
app.get("/api/runtime-policy", (context) => this.getRuntimePolicy(context));
|
|
168
187
|
app.put("/api/runtime-policy", (context) => this.updateRuntimePolicy(context));
|
|
188
|
+
const saas = this.options.saasProject;
|
|
189
|
+
if (saas) {
|
|
190
|
+
app.get("/api/oauth/managed-project", async (context) => context.json(await saas.getState(context.req.raw.signal)));
|
|
191
|
+
app.put("/api/oauth/managed-project", async (context) => context.json(await saas.configure(await readJsonBody(context, 16_384), context.req.raw.signal)));
|
|
192
|
+
app.delete("/api/oauth/managed-project", async (context) => {
|
|
193
|
+
await saas.remove();
|
|
194
|
+
return context.json(await saas.getState(context.req.raw.signal));
|
|
195
|
+
});
|
|
196
|
+
app.get("/api/oauth/managed-project/provider-configs", async (context) => context.json(await saas.listProviderConfigs(context.req.raw.signal)));
|
|
197
|
+
app.get("/api/oauth/sources/:service", async (context) => context.json(await saas.getSource(context.req.param("service"))));
|
|
198
|
+
app.put("/api/oauth/sources/:service", async (context) => context.json(await saas.setSource(context.req.param("service"), await readJsonBody(context, 16_384), context.req.raw.signal)));
|
|
199
|
+
}
|
|
200
|
+
if (saas && this.options.saasOAuth) {
|
|
201
|
+
app.get("/oauth/saas/complete", (context) => {
|
|
202
|
+
context.header("Content-Security-Policy", "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'");
|
|
203
|
+
context.header("Referrer-Policy", "no-referrer");
|
|
204
|
+
return context.html(renderSaasCompletionPage());
|
|
205
|
+
});
|
|
206
|
+
app.post("/api/oauth/connection-requests/:id/sync", async (context) => {
|
|
207
|
+
const expectedOrigin = saas.requireOrigin();
|
|
208
|
+
if (context.req.header("origin") !== expectedOrigin)
|
|
209
|
+
throw new SaasError("oauth_source_origin_mismatch", `Open Console at ${expectedOrigin}, or set OOMOL_CONNECT_ORIGIN to the exact Console address in your browser and restart Connect. localhost and 127.0.0.1 are different origins.`, 403);
|
|
210
|
+
if (context.req.header("x-openconnector-request") !== "sync" ||
|
|
211
|
+
context.req.header("content-type")?.split(";")[0].trim().toLowerCase() !== "application/json")
|
|
212
|
+
return context.json({ error: { code: "forbidden", message: "OAuth synchronization requires a same-origin JSON request." } }, 403);
|
|
213
|
+
const result = await this.options.saasOAuth.syncForBrowser(context.req.param("id"), "local-admin", context.req.raw.signal);
|
|
214
|
+
if (!result)
|
|
215
|
+
return jsonError(context, 404, "connection_request_not_found", "Connection request not found.");
|
|
216
|
+
context.header("Cache-Control", "private, no-store");
|
|
217
|
+
return context.json(result);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
169
220
|
app.get("/api/oauth/configs", (context) => this.listOAuthConfigs(context));
|
|
170
221
|
app.put("/api/oauth/configs/:service", (context) => this.upsertOAuthConfig(context, context.req.param("service")));
|
|
171
222
|
app.delete("/api/oauth/configs/:service", (context) => this.deleteOAuthConfig(context, context.req.param("service")));
|
|
223
|
+
app.post("/api/oauth/connection-requests", async (context) => {
|
|
224
|
+
try {
|
|
225
|
+
const { consoleOAuthConnectionInput } = await import("./api/connection-input.js");
|
|
226
|
+
const parsed = consoleOAuthConnectionInput.safeParse(await readJsonBody(context));
|
|
227
|
+
if (!parsed.success)
|
|
228
|
+
return jsonError(context, 400, "invalid_input", "Invalid OAuth connection request.");
|
|
229
|
+
const { appId, ...input } = parsed.data;
|
|
230
|
+
const target = appId ? await this.options.connections.getStoredConnection(appId) : undefined;
|
|
231
|
+
if (target && (target.service !== input.service || target.connectionName !== input.connectionName))
|
|
232
|
+
return jsonError(context, 400, "invalid_input", "The selected connection no longer matches.");
|
|
233
|
+
return context.json(await this.options.oauthFlow.startConnectionRequest({
|
|
234
|
+
...input,
|
|
235
|
+
target,
|
|
236
|
+
owner: "local-admin",
|
|
237
|
+
signal: context.req.raw.signal,
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error instanceof OAuthClientConfigError ||
|
|
242
|
+
error instanceof OAuthFlowError ||
|
|
243
|
+
error instanceof ConnectionError)
|
|
244
|
+
return jsonError(context, error.code === "unknown_service" || error.code === "app_not_found" ? 404 : 400, error.code, error.message);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
172
248
|
app.post("/api/oauth/authorizations", (context) => this.createOAuthAuthorization(context));
|
|
173
249
|
app.get("/oauth/callback", (context) => this.completeOAuth(context));
|
|
174
250
|
app.post("/mcp", (context) => this.handleMcp(context));
|
|
@@ -181,6 +257,20 @@ export class ConnectServer {
|
|
|
181
257
|
else
|
|
182
258
|
app.notFound(notFound);
|
|
183
259
|
app.onError((error, context) => {
|
|
260
|
+
if (error instanceof SaasError) {
|
|
261
|
+
this.options.logger?.warn({
|
|
262
|
+
method: context.req.method,
|
|
263
|
+
path: context.req.path,
|
|
264
|
+
code: error.code,
|
|
265
|
+
status: error.status,
|
|
266
|
+
reason: error.message,
|
|
267
|
+
}, "SaaS request failed");
|
|
268
|
+
if (error.retryAfter)
|
|
269
|
+
context.header("Retry-After", error.retryAfter);
|
|
270
|
+
if (context.req.path.startsWith("/v1/"))
|
|
271
|
+
return writeRuntimeFailure(context, { status: error.status, errorCode: error.code, message: error.message });
|
|
272
|
+
return context.json({ error: { code: error.code, message: error.message, reason: error.reason } }, error.status);
|
|
273
|
+
}
|
|
184
274
|
if (error instanceof HttpRequestError) {
|
|
185
275
|
if (context.req.path.startsWith("/v1/")) {
|
|
186
276
|
return writeRuntimeFailure(context, {
|
|
@@ -227,7 +317,8 @@ export class ConnectServer {
|
|
|
227
317
|
}
|
|
228
318
|
catch (error) {
|
|
229
319
|
if (error instanceof MarketplaceError) {
|
|
230
|
-
|
|
320
|
+
const status = error.status;
|
|
321
|
+
return jsonError(context, status === 401 || status === 403 || status === 404 || status === 502 || status === 504 ? status : 400, error.code, error.message);
|
|
231
322
|
}
|
|
232
323
|
throw error;
|
|
233
324
|
}
|
|
@@ -254,9 +345,20 @@ export class ConnectServer {
|
|
|
254
345
|
const provider = this.options.catalog.providers.find((provider) => provider.service === service);
|
|
255
346
|
if (!provider)
|
|
256
347
|
return writeRuntimeFailure(context, unknownServiceFailure(service));
|
|
257
|
-
|
|
348
|
+
let oauth = provider.auth.some((auth) => auth.type === "oauth2")
|
|
258
349
|
? await this.options.oauthClientConfigs.getSummary(service)
|
|
259
350
|
: undefined;
|
|
351
|
+
const source = oauth ? await this.options.saasProject?.getSource(service) : undefined;
|
|
352
|
+
if (oauth && source?.mode === "saas") {
|
|
353
|
+
const { config } = await this.options.saasProject.resolveConfig(service, source.providerConfigId, source.managedProjectId, context.req.raw.signal);
|
|
354
|
+
oauth = {
|
|
355
|
+
...oauth,
|
|
356
|
+
configured: true,
|
|
357
|
+
customClientAvailable: false,
|
|
358
|
+
expectedRedirectUri: config.callbackUrl,
|
|
359
|
+
missingFields: [],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
260
362
|
return writeRuntimeSuccess(context, serializeRuntimeProviderSetup(provider, oauth));
|
|
261
363
|
}
|
|
262
364
|
getProvider(context, service) {
|
|
@@ -353,11 +455,6 @@ export class ConnectServer {
|
|
|
353
455
|
catch (error) {
|
|
354
456
|
if (error instanceof ConnectionError) {
|
|
355
457
|
const status = mapConnectionErrorStatus(error);
|
|
356
|
-
// agent.md uses the admin JSON error envelope; mapConnectionErrorStatus may
|
|
357
|
-
// return 409 for OAuth refresh failures, which jsonError does not accept.
|
|
358
|
-
if (status === 409) {
|
|
359
|
-
return context.json({ error: { code: error.code, message: error.message } }, 409);
|
|
360
|
-
}
|
|
361
458
|
return jsonError(context, status, error.code, error.message);
|
|
362
459
|
}
|
|
363
460
|
throw error;
|
|
@@ -428,6 +525,30 @@ export class ConnectServer {
|
|
|
428
525
|
}
|
|
429
526
|
return writeRuntimeSuccess(context, serializeRuntimeAction(action));
|
|
430
527
|
}
|
|
528
|
+
async getRuntimeTriggerPermissions(context, service) {
|
|
529
|
+
const provider = this.options.catalog.providers.find((item) => item.service === service);
|
|
530
|
+
if (!provider)
|
|
531
|
+
throw new HttpRequestError("provider_not_found", "Provider not found.", 404);
|
|
532
|
+
return writeRuntimeSuccess(context, serializeRuntimeTriggerPermissions(provider));
|
|
533
|
+
}
|
|
534
|
+
async executeRuntimeTrigger(context) {
|
|
535
|
+
if (!this.options.triggers)
|
|
536
|
+
throw new HttpRequestError("trigger_not_supported", "Trigger execution is unavailable.", 501);
|
|
537
|
+
const { readTriggerRequest } = await import("../triggers/request.js");
|
|
538
|
+
const request = readTriggerRequest(await readJsonBody(context, 160 * 1024));
|
|
539
|
+
const signal = AbortSignal.any([context.req.raw.signal, AbortSignal.timeout(45_000)]);
|
|
540
|
+
const result = await this.options.triggers.run({
|
|
541
|
+
service: context.req.param("service"),
|
|
542
|
+
triggerId: context.req.param("triggerId"),
|
|
543
|
+
request,
|
|
544
|
+
connectionName: readConnectionName(context),
|
|
545
|
+
connectionId: optionalString(context.req.header("x-oo-connector-app-id")),
|
|
546
|
+
policy: await this.getPolicySnapshot(context),
|
|
547
|
+
grant: readRuntimeGrant(context),
|
|
548
|
+
signal,
|
|
549
|
+
});
|
|
550
|
+
return writeRuntimeSuccess(context, result);
|
|
551
|
+
}
|
|
431
552
|
async createRuntimeActionRun(context, actionId) {
|
|
432
553
|
const action = this.options.catalog.actionsById.get(actionId);
|
|
433
554
|
if (!action) {
|
|
@@ -436,6 +557,7 @@ export class ConnectServer {
|
|
|
436
557
|
const body = await readJsonBody(context);
|
|
437
558
|
const input = body.input ?? {};
|
|
438
559
|
const connectionName = readConnectionName(context, body);
|
|
560
|
+
const connectionId = optionalString(context.req.header("x-oo-connector-app-id"));
|
|
439
561
|
const runtimeGrant = readRuntimeGrant(context);
|
|
440
562
|
let policy;
|
|
441
563
|
try {
|
|
@@ -450,7 +572,7 @@ export class ConnectServer {
|
|
|
450
572
|
});
|
|
451
573
|
}
|
|
452
574
|
if (!policy.evaluate(action).allowed) {
|
|
453
|
-
return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal));
|
|
575
|
+
return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId));
|
|
454
576
|
}
|
|
455
577
|
const idempotencyKey = readIdempotencyKey(context.req.header("idempotency-key"));
|
|
456
578
|
if (!idempotencyKey.ok) {
|
|
@@ -462,7 +584,7 @@ export class ConnectServer {
|
|
|
462
584
|
});
|
|
463
585
|
}
|
|
464
586
|
if (!idempotencyKey.key) {
|
|
465
|
-
return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal));
|
|
587
|
+
return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId));
|
|
466
588
|
}
|
|
467
589
|
const now = new Date();
|
|
468
590
|
const keyHash = hashIdempotencyKey(idempotencyKey.key);
|
|
@@ -471,6 +593,7 @@ export class ConnectServer {
|
|
|
471
593
|
requestHash = hashActionRequest({
|
|
472
594
|
actionId,
|
|
473
595
|
connectionName: connectionName ?? defaultConnectionName,
|
|
596
|
+
connectionId,
|
|
474
597
|
input,
|
|
475
598
|
runtimeTokenId: runtimeGrant?.tokenId,
|
|
476
599
|
});
|
|
@@ -513,7 +636,7 @@ export class ConnectServer {
|
|
|
513
636
|
if (claim.kind === "completed") {
|
|
514
637
|
return writeRuntimeActionHttpResult(context, claim.response);
|
|
515
638
|
}
|
|
516
|
-
const result = await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal);
|
|
639
|
+
const result = await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId);
|
|
517
640
|
const completed = await this.options.idempotency.complete({
|
|
518
641
|
keyHash,
|
|
519
642
|
requestHash,
|
|
@@ -526,13 +649,14 @@ export class ConnectServer {
|
|
|
526
649
|
}
|
|
527
650
|
return writeRuntimeActionHttpResult(context, result);
|
|
528
651
|
}
|
|
529
|
-
async executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, signal) {
|
|
652
|
+
async executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, signal, connectionId) {
|
|
530
653
|
try {
|
|
531
654
|
const run = await this.options.actions.run({
|
|
532
655
|
actionId,
|
|
533
656
|
input,
|
|
534
657
|
caller: "http",
|
|
535
658
|
connectionName,
|
|
659
|
+
connectionId,
|
|
536
660
|
policy,
|
|
537
661
|
runtimeTokenId: runtimeGrant?.tokenId,
|
|
538
662
|
signal,
|
|
@@ -543,6 +667,9 @@ export class ConnectServer {
|
|
|
543
667
|
return serializeRuntimeActionResult({
|
|
544
668
|
actionId,
|
|
545
669
|
executionId: run.executionId,
|
|
670
|
+
remoteExecutionId: run.remoteExecutionId,
|
|
671
|
+
failureStatus: run.failureStatus,
|
|
672
|
+
retryAfter: run.retryAfter,
|
|
546
673
|
auditPersisted: run.auditPersisted,
|
|
547
674
|
result: run.result,
|
|
548
675
|
});
|
|
@@ -591,11 +718,12 @@ export class ConnectServer {
|
|
|
591
718
|
service,
|
|
592
719
|
input: body,
|
|
593
720
|
connectionName: readConnectionName(context, body),
|
|
721
|
+
connectionId: optionalString(context.req.header("x-oo-connector-app-id")),
|
|
594
722
|
policy,
|
|
595
723
|
signal: context.req.raw.signal,
|
|
596
724
|
});
|
|
597
725
|
if (result.ok) {
|
|
598
|
-
return writeRuntimeSuccess(context, result.response);
|
|
726
|
+
return writeRuntimeSuccess(context, result.response, result.meta);
|
|
599
727
|
}
|
|
600
728
|
return writeRuntimeFailure(context, {
|
|
601
729
|
status: result.status,
|
|
@@ -836,7 +964,13 @@ export class ConnectServer {
|
|
|
836
964
|
});
|
|
837
965
|
}
|
|
838
966
|
async listOAuthConfigs(context) {
|
|
839
|
-
|
|
967
|
+
const configs = await this.options.oauthClientConfigs.listConfigs();
|
|
968
|
+
const sources = await this.options.saasProject?.listSources();
|
|
969
|
+
return context.json(configs.map((config) => ({
|
|
970
|
+
...config,
|
|
971
|
+
oauthSource: sources?.get(config.service) ?? { mode: "local" },
|
|
972
|
+
customClientAvailable: sources?.has(config.service) ? false : config.customClientAvailable,
|
|
973
|
+
})));
|
|
840
974
|
}
|
|
841
975
|
async upsertOAuthConfig(context, service) {
|
|
842
976
|
const body = await readJsonBody(context);
|
|
@@ -44,6 +44,8 @@ export interface ConnectorRuntimeOptions {
|
|
|
44
44
|
dataDir: string;
|
|
45
45
|
/** External HTTP(S) URL, optionally including a mount path such as /connector. */
|
|
46
46
|
publicOrigin: string;
|
|
47
|
+
/** False when the host supplied a development fallback instead of an explicit public origin. */
|
|
48
|
+
publicOriginConfigured?: boolean;
|
|
47
49
|
/** Encrypts stored credentials, OAuth client configuration, pending OAuth state and replayed action responses. Omit to store them in plain text. */
|
|
48
50
|
encryptionKey?: string;
|
|
49
51
|
/** Bearer token required for management requests such as connections, OAuth clients and policies. Omit to leave them open. */
|
|
@@ -54,7 +56,7 @@ export interface ConnectorRuntimeOptions {
|
|
|
54
56
|
jwt?: RuntimeJwtConfig;
|
|
55
57
|
postgres?: ConnectorPostgresOptions;
|
|
56
58
|
network?: ConnectorNetworkOptions;
|
|
57
|
-
/** Allow or block actions and
|
|
59
|
+
/** Allow or block actions, proxies and Triggers by name. */
|
|
58
60
|
actionPolicy?: ActionPolicyConfig;
|
|
59
61
|
/** Services, or `*`, whose connections may carry their own OAuth client instead of the configured one. */
|
|
60
62
|
allowedCustomOAuth?: string[];
|
|
@@ -104,13 +104,14 @@ async function openRuntime(options) {
|
|
|
104
104
|
const tempDir = join(dataDir, "tmp/transit-files");
|
|
105
105
|
await transitFiles.cleanupExpired();
|
|
106
106
|
await cleanupStagedTransitFiles(tempDir, ttlSeconds * 1000);
|
|
107
|
-
const { app, runtimeAuthConfigured } = await createConnectApp({
|
|
107
|
+
const { app, runtimeAuthConfigured, saasCleanup, triggerMaintenance } = await createConnectApp({
|
|
108
108
|
catalog,
|
|
109
109
|
providerLoader: new ProviderLoader(executorModules),
|
|
110
110
|
runtimeDatabase: database,
|
|
111
111
|
transitFiles,
|
|
112
112
|
uploadTransitFile: createNodeTransitFileUpload({ transitFiles, tempDir }),
|
|
113
113
|
publicOrigin,
|
|
114
|
+
configuredOrigin: options.publicOriginConfigured === false ? undefined : publicOrigin,
|
|
114
115
|
secretCodec,
|
|
115
116
|
adminToken: options.adminToken,
|
|
116
117
|
runtimeToken: options.runtimeToken,
|
|
@@ -120,6 +121,8 @@ async function openRuntime(options) {
|
|
|
120
121
|
logger: options.logger,
|
|
121
122
|
serveDocumentation: options.apiReference ?? false,
|
|
122
123
|
});
|
|
124
|
+
saasCleanup.start();
|
|
125
|
+
triggerMaintenance.start();
|
|
123
126
|
const shutdown = new AbortController();
|
|
124
127
|
const pending = new Set();
|
|
125
128
|
let closing;
|
|
@@ -142,7 +145,7 @@ async function openRuntime(options) {
|
|
|
142
145
|
if (!closing) {
|
|
143
146
|
closing = Promise.resolve().then(async () => {
|
|
144
147
|
shutdown.abort(new Error("Open Connector runtime is closing."));
|
|
145
|
-
await Promise.allSettled([...pending]);
|
|
148
|
+
await Promise.allSettled([...pending, saasCleanup.close(), triggerMaintenance.close()]);
|
|
146
149
|
try {
|
|
147
150
|
await database.close();
|
|
148
151
|
}
|
|
@@ -130,13 +130,22 @@ export class TransitFileService {
|
|
|
130
130
|
async writeStream(file, tempPath) {
|
|
131
131
|
let sizeBytes = 0;
|
|
132
132
|
const maxBytes = this.maxBytes;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
const source = Readable.fromWeb(file.body);
|
|
134
|
+
const destination = createWriteStream(tempPath, { flags: "wx" });
|
|
135
|
+
// Pipeline can reject while the file is still opening. Wait for close before unlinking it.
|
|
136
|
+
const closed = new Promise((resolve) => destination.once("close", resolve));
|
|
137
|
+
try {
|
|
138
|
+
await pipeline(source, async function* (source) {
|
|
139
|
+
for await (const chunk of source) {
|
|
140
|
+
sizeBytes += chunk.byteLength;
|
|
141
|
+
assertFileSize(sizeBytes, maxBytes);
|
|
142
|
+
yield chunk;
|
|
143
|
+
}
|
|
144
|
+
}, destination, { signal: file.signal });
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await closed;
|
|
148
|
+
}
|
|
140
149
|
return sizeBytes;
|
|
141
150
|
}
|
|
142
151
|
}
|
|
@@ -3,17 +3,20 @@ import type { ConnectionService } from "../../connection-service.ts";
|
|
|
3
3
|
import type { ActionPolicySnapshot } from "../../core/action-policy.ts";
|
|
4
4
|
import type { RuntimeLogger, ProxyResponse } from "../../core/types.ts";
|
|
5
5
|
import type { IProviderLoader } from "../../providers/provider-loader.ts";
|
|
6
|
-
|
|
6
|
+
import type { SaasExecutionService } from "../../saas/saas-execution-service.ts";
|
|
7
|
+
export type ProxyFailureStatus = 400 | 402 | 403 | 404 | 409 | 413 | 429 | 500 | 501 | 502 | 503 | 504;
|
|
7
8
|
export interface ProxyRunnerOptions {
|
|
8
9
|
catalog: CatalogStore;
|
|
9
10
|
providerLoader: IProviderLoader;
|
|
10
11
|
connections: ConnectionService;
|
|
11
12
|
logger?: RuntimeLogger;
|
|
13
|
+
saas?: SaasExecutionService;
|
|
12
14
|
}
|
|
13
15
|
export interface RunProxyInput {
|
|
14
16
|
service: string;
|
|
15
17
|
input: unknown;
|
|
16
18
|
connectionName?: string;
|
|
19
|
+
connectionId?: string;
|
|
17
20
|
policy: ActionPolicySnapshot;
|
|
18
21
|
/** Cancellation signal from the HTTP request, handed to the provider proxy executor. */
|
|
19
22
|
signal?: AbortSignal;
|
|
@@ -21,6 +24,7 @@ export interface RunProxyInput {
|
|
|
21
24
|
export type ProxyRunResult = {
|
|
22
25
|
ok: true;
|
|
23
26
|
response: ProxyResponse;
|
|
27
|
+
meta?: Record<string, unknown>;
|
|
24
28
|
} | ProxyRunFailure;
|
|
25
29
|
export interface ProxyRunFailure {
|
|
26
30
|
ok: false;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConnectionError } from "../../connection-service.js";
|
|
2
2
|
import { optionalInteger, optionalRecord, requiredRecord, requiredString } from "../../core/cast.js";
|
|
3
|
+
import { SaasError } from "../../saas/saas-client.js";
|
|
3
4
|
import { mapConnectionErrorStatus } from "../api/runtime-api.js";
|
|
4
5
|
class ProxyInputError extends Error {
|
|
5
6
|
}
|
|
@@ -30,29 +31,6 @@ export class ProxyRunner {
|
|
|
30
31
|
meta: { service: provider.service },
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
|
-
let executor;
|
|
34
|
-
try {
|
|
35
|
-
executor = await this.options.providerLoader.loadProxyExecutor(provider.service, provider.displayName);
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
this.options.logger?.warn({ service: provider.service, errorCode: "internal_error" }, "proxy request failed");
|
|
39
|
-
return {
|
|
40
|
-
ok: false,
|
|
41
|
-
status: 500,
|
|
42
|
-
errorCode: "internal_error",
|
|
43
|
-
message: "Proxy request failed unexpectedly.",
|
|
44
|
-
meta: { service: provider.service },
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (!executor) {
|
|
48
|
-
return {
|
|
49
|
-
ok: false,
|
|
50
|
-
status: 501,
|
|
51
|
-
errorCode: "proxy_not_supported",
|
|
52
|
-
message: `Proxy execution is not supported for ${provider.service}.`,
|
|
53
|
-
meta: { service: provider.service },
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
34
|
const request = this.readProxyRequestInput(input.input);
|
|
57
35
|
if (!request.ok) {
|
|
58
36
|
return request;
|
|
@@ -64,8 +42,9 @@ export class ProxyRunner {
|
|
|
64
42
|
connectionName: input.connectionName,
|
|
65
43
|
};
|
|
66
44
|
const startedAtMs = Date.now();
|
|
45
|
+
let executionId;
|
|
67
46
|
try {
|
|
68
|
-
const connection = await this.options.connections.getConnectionSummary(provider.service, input.connectionName);
|
|
47
|
+
const connection = await this.options.connections.getConnectionSummary(provider.service, input.connectionName, input.connectionId);
|
|
69
48
|
const connectionDecision = connection?.authType === "no_auth" ? undefined : input.policy.evaluateConnection(connection?.id);
|
|
70
49
|
if (connectionDecision && !connectionDecision.allowed) {
|
|
71
50
|
return {
|
|
@@ -76,10 +55,58 @@ export class ProxyRunner {
|
|
|
76
55
|
meta: { service: provider.service },
|
|
77
56
|
};
|
|
78
57
|
}
|
|
58
|
+
const target = await this.options.connections.resolveForExecution(provider.service, input.connectionName, input.connectionId);
|
|
59
|
+
const targetDecision = target.summary?.authType === "no_auth" ? undefined : input.policy.evaluateConnection(target.summary?.id);
|
|
60
|
+
if (targetDecision && !targetDecision.allowed)
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
status: 403,
|
|
64
|
+
errorCode: targetDecision.code,
|
|
65
|
+
message: targetDecision.message,
|
|
66
|
+
meta: { service: provider.service },
|
|
67
|
+
};
|
|
79
68
|
this.options.logger?.info(logContext, "proxy request started");
|
|
80
|
-
|
|
69
|
+
if (target.kind === "saas") {
|
|
70
|
+
executionId = crypto.randomUUID();
|
|
71
|
+
input.signal?.throwIfAborted();
|
|
72
|
+
if (!this.options.saas)
|
|
73
|
+
throw new SaasError("oauth_source_unavailable", "SaaS execution is unavailable.", 503);
|
|
74
|
+
const { alias: _alias, connectionName: _connectionName, ...remoteRequest } = optionalRecord(input.input);
|
|
75
|
+
remoteRequest.method = request.input.method;
|
|
76
|
+
const remote = await this.options.saas.executeProxy(target.reference, provider.service, remoteRequest, input.signal);
|
|
77
|
+
input.signal?.throwIfAborted();
|
|
78
|
+
this.options.logger?.info({
|
|
79
|
+
...logContext,
|
|
80
|
+
executionId,
|
|
81
|
+
remoteExecutionId: remote.executionId,
|
|
82
|
+
durationMs: Date.now() - startedAtMs,
|
|
83
|
+
status: remote.response.status,
|
|
84
|
+
}, "proxy request completed");
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
response: remote.response,
|
|
88
|
+
meta: { service: provider.service, executionId, remoteExecutionId: remote.executionId },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (target.kind === "marketplace")
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
status: 501,
|
|
95
|
+
errorCode: "proxy_not_supported",
|
|
96
|
+
message: "Marketplace connections do not support proxy execution.",
|
|
97
|
+
meta: { service: provider.service },
|
|
98
|
+
};
|
|
99
|
+
const executor = await this.options.providerLoader.loadProxyExecutor(provider.service, provider.displayName);
|
|
100
|
+
if (!executor)
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
status: 501,
|
|
104
|
+
errorCode: "proxy_not_supported",
|
|
105
|
+
message: `Proxy execution is not supported for ${provider.service}.`,
|
|
106
|
+
meta: { service: provider.service },
|
|
107
|
+
};
|
|
81
108
|
const result = await executor(request.input, {
|
|
82
|
-
getCredential:
|
|
109
|
+
getCredential: target.getCredential,
|
|
83
110
|
signal: input.signal,
|
|
84
111
|
});
|
|
85
112
|
const durationMs = Date.now() - startedAtMs;
|
|
@@ -103,6 +130,25 @@ export class ProxyRunner {
|
|
|
103
130
|
}
|
|
104
131
|
catch (error) {
|
|
105
132
|
const durationMs = Date.now() - startedAtMs;
|
|
133
|
+
if (executionId && input.signal?.aborted)
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
status: 400,
|
|
137
|
+
errorCode: "execution_cancelled",
|
|
138
|
+
message: "Proxy execution was cancelled.",
|
|
139
|
+
meta: { service: provider.service, executionId },
|
|
140
|
+
};
|
|
141
|
+
if (error instanceof SaasError) {
|
|
142
|
+
this.options.logger?.warn({ ...logContext, durationMs, executionId, errorCode: error.code, remoteExecutionId: error.remoteExecutionId }, "proxy request failed");
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
status: error.status,
|
|
146
|
+
errorCode: error.code,
|
|
147
|
+
message: error.message,
|
|
148
|
+
data: error.retryAfter ? { details: { retryAfter: error.retryAfter } } : undefined,
|
|
149
|
+
meta: { service: provider.service, executionId, remoteExecutionId: error.remoteExecutionId },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
106
152
|
if (error instanceof ConnectionError) {
|
|
107
153
|
const missingConnectionDecision = error.code === "connection_not_found" ? input.policy.evaluateConnection() : undefined;
|
|
108
154
|
if (missingConnectionDecision && !missingConnectionDecision.allowed) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StoredConnection } from "../../connection-service.ts";
|
|
1
|
+
import type { StoredConnection, StoredSaasConnection } from "../../connection-service.ts";
|
|
2
2
|
import type { ResolvedCredential } from "../../core/types.ts";
|
|
3
3
|
import type { OAuthAuthorizationState } from "../../oauth/oauth-flow-service.ts";
|
|
4
4
|
import type { ISecretCodec } from "../secrets/secret-codec-core.ts";
|
|
@@ -26,6 +26,35 @@ export interface RequestStatement {
|
|
|
26
26
|
sql: string;
|
|
27
27
|
values: (string | number | null)[];
|
|
28
28
|
}
|
|
29
|
+
export interface PendingSaasRequest {
|
|
30
|
+
connectionRequestId: string;
|
|
31
|
+
connectionId: string;
|
|
32
|
+
owner: string;
|
|
33
|
+
service: string;
|
|
34
|
+
connectionName: string;
|
|
35
|
+
managedProjectId: string;
|
|
36
|
+
providerConfigId: string;
|
|
37
|
+
externalUserId: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
expiresAt: string;
|
|
40
|
+
returnUri?: string;
|
|
41
|
+
target?: Pick<StoredConnection, "id" | "revision">;
|
|
42
|
+
comment?: string | null;
|
|
43
|
+
}
|
|
44
|
+
export interface SaasRequestLease {
|
|
45
|
+
pending: PendingSaasRequest;
|
|
46
|
+
leaseId: string;
|
|
47
|
+
phase: "creating" | "pending" | "candidate";
|
|
48
|
+
remoteRequestId: string | null;
|
|
49
|
+
candidate?: SaasCandidate;
|
|
50
|
+
pollAttempts: number;
|
|
51
|
+
}
|
|
52
|
+
export interface SaasCandidate {
|
|
53
|
+
connectedAccountId: string;
|
|
54
|
+
profile: StoredSaasConnection["profile"];
|
|
55
|
+
status: StoredSaasConnection["status"];
|
|
56
|
+
comment: string | null;
|
|
57
|
+
}
|
|
29
58
|
/** Executes all statements atomically; each result contains its RETURNING/SELECT rows. */
|
|
30
59
|
export type RequestTransaction = (statements: RequestStatement[]) => Promise<RuntimeRow[][]>;
|
|
31
60
|
/** Shared SQL lifecycle for SQLite, PostgreSQL and D1. Secrets use the runtime's codec. */
|
|
@@ -38,4 +67,15 @@ export declare class ConnectionRequestStore {
|
|
|
38
67
|
claim(state: string): Promise<PendingConnectionRequest | undefined>;
|
|
39
68
|
fail(id: string, errorCode: string, errorMessage: string): Promise<void>;
|
|
40
69
|
complete(pending: PendingConnectionRequest, credential: ResolvedCredential, signal?: AbortSignal): Promise<string | undefined>;
|
|
70
|
+
private retireRequests;
|
|
71
|
+
createSaas(pending: PendingSaasRequest): Promise<SaasRequestLease>;
|
|
72
|
+
claimSaas(id: string, owner: string, now?: number): Promise<SaasRequestLease | undefined>;
|
|
73
|
+
saveSaasRequest(lease: SaasRequestLease, remoteRequestId: string, expiresAt?: string): Promise<boolean>;
|
|
74
|
+
saveSaasCandidate(lease: SaasRequestLease, candidate: SaasCandidate): Promise<boolean>;
|
|
75
|
+
releaseSaas(lease: SaasRequestLease, nextPollAt: number, failed?: boolean): Promise<boolean>;
|
|
76
|
+
failSaas(lease: SaasRequestLease, code: string, message: string): Promise<boolean>;
|
|
77
|
+
getSaasReturnUri(id: string, owner: string): Promise<string | undefined>;
|
|
78
|
+
cancelSaas(id: string, owner: string): Promise<void>;
|
|
79
|
+
expireSaas(now?: number): Promise<void>;
|
|
80
|
+
completeSaas(lease: SaasRequestLease): Promise<"connected" | "conflict" | "lease_lost">;
|
|
41
81
|
}
|