@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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { CatalogStore } from "../catalog-store.ts";
|
|
2
|
+
import type { ISecretCodec } from "../server/secrets/secret-codec-core.ts";
|
|
3
|
+
import type { ManagedProject, SaasProjectStore } from "../server/storage/saas-project-store.ts";
|
|
4
|
+
import type { SaasProviderConfig } from "./saas-client.ts";
|
|
5
|
+
import { SaasClient } from "./saas-client.ts";
|
|
6
|
+
export interface SaasProjectState {
|
|
7
|
+
configured: boolean;
|
|
8
|
+
managedProjectId: string | null;
|
|
9
|
+
projectId: string | null;
|
|
10
|
+
baseUrl: string | null;
|
|
11
|
+
status: "unconfigured" | "available" | "unavailable" | "auth_error";
|
|
12
|
+
cleanup: {
|
|
13
|
+
pending: number;
|
|
14
|
+
manual: number;
|
|
15
|
+
paused: boolean;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export type OAuthSource = {
|
|
19
|
+
mode: "local";
|
|
20
|
+
} | {
|
|
21
|
+
mode: "saas";
|
|
22
|
+
managedProjectId: string;
|
|
23
|
+
projectId: string;
|
|
24
|
+
providerConfigId: string;
|
|
25
|
+
};
|
|
26
|
+
export interface SaasProjectServiceOptions {
|
|
27
|
+
catalog: CatalogStore;
|
|
28
|
+
store: SaasProjectStore;
|
|
29
|
+
secretCodec: ISecretCodec;
|
|
30
|
+
configuredOrigin?: string;
|
|
31
|
+
client?: SaasClient;
|
|
32
|
+
}
|
|
33
|
+
/** Owns configuration and capability checks; account lifecycle stays in the connection request service. */
|
|
34
|
+
export declare class SaasProjectService {
|
|
35
|
+
private readonly options;
|
|
36
|
+
private readonly client;
|
|
37
|
+
constructor(options: SaasProjectServiceOptions);
|
|
38
|
+
requireOrigin(): string;
|
|
39
|
+
externalUserId(): Promise<string>;
|
|
40
|
+
getState(signal?: AbortSignal): Promise<SaasProjectState>;
|
|
41
|
+
configure(input: unknown, signal?: AbortSignal): Promise<SaasProjectState>;
|
|
42
|
+
remove(): Promise<void>;
|
|
43
|
+
listProviderConfigs(signal?: AbortSignal): Promise<{
|
|
44
|
+
projectId: string;
|
|
45
|
+
providerConfigs: SaasProviderConfig[];
|
|
46
|
+
}>;
|
|
47
|
+
listSources(): Promise<Map<string, OAuthSource>>;
|
|
48
|
+
getSource(service: string): Promise<OAuthSource>;
|
|
49
|
+
setSource(service: string, input: unknown, signal?: AbortSignal): Promise<OAuthSource>;
|
|
50
|
+
resolveConfig(service: string, configId: string, managedProjectId?: string, signal?: AbortSignal): Promise<{
|
|
51
|
+
project: ManagedProject;
|
|
52
|
+
config: SaasProviderConfig;
|
|
53
|
+
}>;
|
|
54
|
+
resolveExecutionConfig(service: string, configId: string, managedProjectId: string | undefined, signal?: AbortSignal): Promise<{
|
|
55
|
+
project: ManagedProject;
|
|
56
|
+
config: SaasProviderConfig;
|
|
57
|
+
}>;
|
|
58
|
+
requireProject(managedProjectId?: string): Promise<ManagedProject>;
|
|
59
|
+
private discover;
|
|
60
|
+
private getProvider;
|
|
61
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { normalizeSaasBaseUrl, SaasClient, SaasError } from "./saas-client.js";
|
|
2
|
+
/** Owns configuration and capability checks; account lifecycle stays in the connection request service. */
|
|
3
|
+
export class SaasProjectService {
|
|
4
|
+
options;
|
|
5
|
+
client;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.client = options.client ?? new SaasClient();
|
|
9
|
+
}
|
|
10
|
+
requireOrigin() {
|
|
11
|
+
try {
|
|
12
|
+
const value = this.options.configuredOrigin;
|
|
13
|
+
if (!value)
|
|
14
|
+
throw new Error();
|
|
15
|
+
const url = new URL(value);
|
|
16
|
+
if (!["http:", "https:"].includes(url.protocol) ||
|
|
17
|
+
url.username ||
|
|
18
|
+
url.password ||
|
|
19
|
+
url.search ||
|
|
20
|
+
url.hash ||
|
|
21
|
+
url.pathname !== "/")
|
|
22
|
+
throw new Error();
|
|
23
|
+
return url.origin;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new SaasError("oauth_source_configuration_error", "Set OOMOL_CONNECT_ORIGIN to an explicit HTTP(S) origin before enabling SaaS OAuth.", 400, undefined, "origin_required");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async externalUserId() {
|
|
30
|
+
return `open-connector:${await this.options.store.getInstanceId()}`;
|
|
31
|
+
}
|
|
32
|
+
async getState(signal) {
|
|
33
|
+
const project = await this.options.store.getProject();
|
|
34
|
+
const cleanup = await this.options.store.getCleanupStats();
|
|
35
|
+
if (!project)
|
|
36
|
+
return {
|
|
37
|
+
configured: false,
|
|
38
|
+
managedProjectId: null,
|
|
39
|
+
projectId: null,
|
|
40
|
+
baseUrl: null,
|
|
41
|
+
status: "unconfigured",
|
|
42
|
+
cleanup,
|
|
43
|
+
};
|
|
44
|
+
let status = "available";
|
|
45
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
46
|
+
try {
|
|
47
|
+
await this.discover(project, signal);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
signal?.throwIfAborted();
|
|
52
|
+
if (!(error instanceof SaasError))
|
|
53
|
+
throw error;
|
|
54
|
+
if (attempt === 0 &&
|
|
55
|
+
error.code === "oauth_source_unavailable" &&
|
|
56
|
+
(error.reason === undefined || ["network", "dns", "timeout"].includes(error.reason)))
|
|
57
|
+
continue;
|
|
58
|
+
status = error.code === "oauth_source_unauthorized" ? "auth_error" : "unavailable";
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
configured: true,
|
|
64
|
+
managedProjectId: project.id,
|
|
65
|
+
projectId: project.projectId,
|
|
66
|
+
baseUrl: project.baseUrl,
|
|
67
|
+
status,
|
|
68
|
+
cleanup,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async configure(input, signal) {
|
|
72
|
+
const { z } = await import("zod");
|
|
73
|
+
const projectInput = z.strictObject({
|
|
74
|
+
baseUrl: z.string().min(1),
|
|
75
|
+
projectApiKey: z
|
|
76
|
+
.string()
|
|
77
|
+
.trim()
|
|
78
|
+
.min(1)
|
|
79
|
+
.regex(/^[^\r\n]+$/),
|
|
80
|
+
});
|
|
81
|
+
const parsed = projectInput.safeParse(input);
|
|
82
|
+
if (!parsed.success)
|
|
83
|
+
throw new SaasError("invalid_input", "Provide baseUrl and projectApiKey only.");
|
|
84
|
+
if (!this.options.secretCodec.encrypted)
|
|
85
|
+
throw new SaasError("oauth_source_configuration_error", "Configure encryption before saving a SaaS project key.", 400, undefined, "encryption_required");
|
|
86
|
+
this.requireOrigin();
|
|
87
|
+
const baseUrl = normalizeSaasBaseUrl(parsed.data.baseUrl);
|
|
88
|
+
const current = await this.options.store.getProject();
|
|
89
|
+
if (current && current.baseUrl !== baseUrl)
|
|
90
|
+
throw new SaasError("oauth_source_mismatch", "Remove the existing project before changing its origin.", 409);
|
|
91
|
+
const discovery = await this.client.discover({ baseUrl, apiKey: parsed.data.projectApiKey }, signal);
|
|
92
|
+
const project = {
|
|
93
|
+
id: current?.id ?? crypto.randomUUID(),
|
|
94
|
+
projectId: discovery.projectId,
|
|
95
|
+
baseUrl,
|
|
96
|
+
apiKey: parsed.data.projectApiKey,
|
|
97
|
+
};
|
|
98
|
+
if (!(await this.options.store.saveProject(project)))
|
|
99
|
+
throw new SaasError("oauth_source_mismatch", "The project identity changed. Reload the configuration before retrying.", 409);
|
|
100
|
+
return {
|
|
101
|
+
configured: true,
|
|
102
|
+
managedProjectId: project.id,
|
|
103
|
+
projectId: project.projectId,
|
|
104
|
+
baseUrl,
|
|
105
|
+
status: "available",
|
|
106
|
+
cleanup: await this.options.store.getCleanupStats(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async remove() {
|
|
110
|
+
const project = await this.options.store.getProject();
|
|
111
|
+
if (project && !(await this.options.store.deleteProject(project.id)))
|
|
112
|
+
throw new SaasError("oauth_source_in_use", "Clear service defaults, connections, pending requests and cleanup tasks before removing this project.", 409);
|
|
113
|
+
}
|
|
114
|
+
async listProviderConfigs(signal) {
|
|
115
|
+
const project = await this.requireProject();
|
|
116
|
+
const discovery = await this.discover(project, signal);
|
|
117
|
+
return {
|
|
118
|
+
projectId: project.projectId,
|
|
119
|
+
providerConfigs: discovery.providerConfigs.flatMap((config) => {
|
|
120
|
+
const provider = this.options.catalog.providers.find((provider) => provider.service === config.service);
|
|
121
|
+
if (!provider?.auth.some((auth) => auth.type === "oauth2"))
|
|
122
|
+
return [];
|
|
123
|
+
const actionIds = config.actionIds.filter((id) => this.options.catalog.actionsById.get(id)?.service === config.service);
|
|
124
|
+
return [{ ...config, actionIds }];
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async listSources() {
|
|
129
|
+
return new Map((await this.options.store.listSources()).map(({ service, ...source }) => [service, { mode: "saas", ...source }]));
|
|
130
|
+
}
|
|
131
|
+
async getSource(service) {
|
|
132
|
+
this.getProvider(service);
|
|
133
|
+
const source = await this.options.store.getSource(service);
|
|
134
|
+
if (!source)
|
|
135
|
+
return { mode: "local" };
|
|
136
|
+
const project = await this.requireProject();
|
|
137
|
+
if (project.id !== source.managedProjectId)
|
|
138
|
+
throw new SaasError("oauth_source_mismatch", "The configured OAuth source no longer matches the project.", 409);
|
|
139
|
+
return {
|
|
140
|
+
mode: "saas",
|
|
141
|
+
managedProjectId: source.managedProjectId,
|
|
142
|
+
projectId: project.projectId,
|
|
143
|
+
providerConfigId: source.providerConfigId,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async setSource(service, input, signal) {
|
|
147
|
+
this.getProvider(service);
|
|
148
|
+
const { z } = await import("zod");
|
|
149
|
+
const sourceInput = z.discriminatedUnion("mode", [
|
|
150
|
+
z.strictObject({ mode: z.literal("local") }),
|
|
151
|
+
z.strictObject({ mode: z.literal("saas"), providerConfigId: z.string().min(1) }),
|
|
152
|
+
]);
|
|
153
|
+
const parsed = sourceInput.safeParse(input);
|
|
154
|
+
if (!parsed.success)
|
|
155
|
+
throw new SaasError("invalid_input", "Use mode local, or mode saas with providerConfigId.");
|
|
156
|
+
if (parsed.data.mode === "local") {
|
|
157
|
+
await this.options.store.setSource(service, undefined);
|
|
158
|
+
return { mode: "local" };
|
|
159
|
+
}
|
|
160
|
+
const { project, config } = await this.resolveConfig(service, parsed.data.providerConfigId, undefined, signal);
|
|
161
|
+
if (!(await this.options.store.setSource(service, { managedProjectId: project.id, providerConfigId: config.id })))
|
|
162
|
+
throw new SaasError("oauth_source_mismatch", "The project was removed while selecting the OAuth source.", 409);
|
|
163
|
+
return { mode: "saas", managedProjectId: project.id, projectId: project.projectId, providerConfigId: config.id };
|
|
164
|
+
}
|
|
165
|
+
async resolveConfig(service, configId, managedProjectId, signal) {
|
|
166
|
+
const provider = this.getProvider(service);
|
|
167
|
+
this.requireOrigin();
|
|
168
|
+
const { project, config } = await this.resolveExecutionConfig(service, configId, managedProjectId, signal);
|
|
169
|
+
const auth = provider.auth.find((auth) => auth.type === "oauth2");
|
|
170
|
+
const required = new Set(auth.authorizationOptions?.filter((option) => option.required).map((option) => option.id));
|
|
171
|
+
for (const id of required)
|
|
172
|
+
for (const dependency of auth.authorizationOptions?.find((option) => option.id === id)?.requires ?? [])
|
|
173
|
+
required.add(dependency);
|
|
174
|
+
if ([...required].some((scope) => !config.effectiveScopes.includes(scope)))
|
|
175
|
+
throw new SaasError("oauth_source_scope_missing", "The SaaS configuration is missing required OAuth scopes.");
|
|
176
|
+
const supportsAction = config.actionIds.some((id) => this.options.catalog.actionsById.get(id)?.service === service);
|
|
177
|
+
if (!supportsAction && !config.proxyAvailable)
|
|
178
|
+
throw new SaasError("oauth_source_unsupported", "This configuration has no compatible actions or proxy capability.");
|
|
179
|
+
return { project, config };
|
|
180
|
+
}
|
|
181
|
+
async resolveExecutionConfig(service, configId, managedProjectId, signal) {
|
|
182
|
+
const project = await this.requireProject(managedProjectId);
|
|
183
|
+
const discovery = await this.discover(project, signal);
|
|
184
|
+
const config = discovery.providerConfigs.find((config) => config.id === configId && config.service === service);
|
|
185
|
+
if (!config)
|
|
186
|
+
throw new SaasError("oauth_source_mismatch", "Select an available OAuth configuration for this service.", 409);
|
|
187
|
+
return { project, config };
|
|
188
|
+
}
|
|
189
|
+
async requireProject(managedProjectId) {
|
|
190
|
+
const project = await this.options.store.getProject();
|
|
191
|
+
if (!project)
|
|
192
|
+
throw new SaasError("oauth_source_not_configured", "Configure a SaaS project first.");
|
|
193
|
+
if (managedProjectId && managedProjectId !== project.id)
|
|
194
|
+
throw new SaasError("oauth_source_mismatch", "The OAuth project binding no longer matches.", 409);
|
|
195
|
+
return project;
|
|
196
|
+
}
|
|
197
|
+
async discover(project, signal) {
|
|
198
|
+
const discovery = await this.client.discover(project, signal);
|
|
199
|
+
if (discovery.projectId !== project.projectId)
|
|
200
|
+
throw new SaasError("oauth_source_mismatch", "SaaS project identity does not match the saved configuration.", 409);
|
|
201
|
+
return discovery;
|
|
202
|
+
}
|
|
203
|
+
getProvider(service) {
|
|
204
|
+
const provider = this.options.catalog.providers.find((provider) => provider.service === service);
|
|
205
|
+
if (!provider)
|
|
206
|
+
throw new SaasError("unknown_service", "Provider was not found.", 404);
|
|
207
|
+
if (!provider.auth.some((auth) => auth.type === "oauth2"))
|
|
208
|
+
throw new SaasError("unsupported_auth_type", "This provider does not support OAuth.");
|
|
209
|
+
return provider;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -42,6 +42,7 @@ export function hashActionRequest(input) {
|
|
|
42
42
|
return sha256(JSON.stringify({
|
|
43
43
|
actionId: input.actionId,
|
|
44
44
|
connectionName: input.connectionName,
|
|
45
|
+
connectionId: input.connectionId,
|
|
45
46
|
input: canonicalize(input.input, 1),
|
|
46
47
|
runtimeTokenId: input.runtimeTokenId,
|
|
47
48
|
}));
|
|
@@ -4,7 +4,9 @@ import type { ActionPolicySnapshot } from "../../core/action-policy.ts";
|
|
|
4
4
|
import type { RuntimeLogger, ExecutionResult, TransitFileWriter } from "../../core/types.ts";
|
|
5
5
|
import type { MarketplaceService } from "../../marketplace/marketplace-service.ts";
|
|
6
6
|
import type { IProviderLoader } from "../../providers/provider-loader.ts";
|
|
7
|
+
import type { SaasExecutionService } from "../../saas/saas-execution-service.ts";
|
|
7
8
|
import type { IRunLogStore, RunLog, RunLogCaller, RunLogListInput, RunLogPage } from "../storage/runtime-store.ts";
|
|
9
|
+
import { SaasError } from "../../saas/saas-client.ts";
|
|
8
10
|
export interface ActionRunnerOptions {
|
|
9
11
|
catalog: CatalogStore;
|
|
10
12
|
providerLoader: IProviderLoader;
|
|
@@ -13,12 +15,14 @@ export interface ActionRunnerOptions {
|
|
|
13
15
|
transitFiles?: TransitFileWriter;
|
|
14
16
|
logger?: RuntimeLogger;
|
|
15
17
|
marketplace?: MarketplaceService;
|
|
18
|
+
saas?: SaasExecutionService;
|
|
16
19
|
}
|
|
17
20
|
export interface RunActionInput {
|
|
18
21
|
actionId: string;
|
|
19
22
|
input: unknown;
|
|
20
23
|
caller: RunLogCaller;
|
|
21
24
|
connectionName?: string;
|
|
25
|
+
connectionId?: string;
|
|
22
26
|
policy: ActionPolicySnapshot;
|
|
23
27
|
runtimeTokenId?: string;
|
|
24
28
|
signal?: AbortSignal;
|
|
@@ -26,6 +30,9 @@ export interface RunActionInput {
|
|
|
26
30
|
export interface ActionRunResult {
|
|
27
31
|
executionId: string;
|
|
28
32
|
auditPersisted: boolean;
|
|
33
|
+
remoteExecutionId?: string;
|
|
34
|
+
failureStatus?: SaasError["status"];
|
|
35
|
+
retryAfter?: string;
|
|
29
36
|
result: ExecutionResult;
|
|
30
37
|
connection?: ConnectionSummary;
|
|
31
38
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConnectionError } from "../../connection-service.js";
|
|
2
2
|
import { executeAction as executeProviderAction } from "../../core/execution.js";
|
|
3
|
+
import { SaasError } from "../../saas/saas-client.js";
|
|
3
4
|
import { safeRunLogError, summarizeForRunLog } from "./run-log-summary.js";
|
|
4
5
|
/**
|
|
5
6
|
* Shared execution boundary for HTTP, MCP, and future local callers.
|
|
@@ -32,6 +33,9 @@ export class ActionRunner {
|
|
|
32
33
|
let policy = input.policy.evaluate(action);
|
|
33
34
|
let connection;
|
|
34
35
|
let result;
|
|
36
|
+
let remoteExecutionId;
|
|
37
|
+
let failureStatus;
|
|
38
|
+
let retryAfter;
|
|
35
39
|
if (!policy.allowed) {
|
|
36
40
|
result = { ok: false, error: { code: policy.code, message: policy.message } };
|
|
37
41
|
}
|
|
@@ -40,7 +44,7 @@ export class ActionRunner {
|
|
|
40
44
|
}
|
|
41
45
|
else {
|
|
42
46
|
try {
|
|
43
|
-
const summary = await this.options.connections.getConnectionSummary(action.service, input.connectionName);
|
|
47
|
+
const summary = await this.options.connections.getConnectionSummary(action.service, input.connectionName, input.connectionId);
|
|
44
48
|
input.signal?.throwIfAborted();
|
|
45
49
|
const connectionPolicy = summary?.authType === "no_auth" ? undefined : input.policy.evaluateConnection(summary?.id);
|
|
46
50
|
if (connectionPolicy && !connectionPolicy.allowed) {
|
|
@@ -57,15 +61,31 @@ export class ActionRunner {
|
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
else {
|
|
60
|
-
connection = await this.options.connections.resolveForExecution(action.service, input.connectionName);
|
|
64
|
+
connection = await this.options.connections.resolveForExecution(action.service, input.connectionName, input.connectionId);
|
|
61
65
|
input.signal?.throwIfAborted();
|
|
62
|
-
const
|
|
66
|
+
const targetPolicy = connection.summary?.authType === "no_auth"
|
|
67
|
+
? undefined
|
|
68
|
+
: input.policy.evaluateConnection(connection.summary?.id);
|
|
69
|
+
if (targetPolicy && !targetPolicy.allowed) {
|
|
70
|
+
policy = targetPolicy;
|
|
71
|
+
throw new ConnectionError(targetPolicy.code, targetPolicy.message);
|
|
72
|
+
}
|
|
73
|
+
const executor = action.execution.locallyExecutable && connection.kind === "local"
|
|
63
74
|
? await this.options.providerLoader.loadActionExecutor(action.service, action.id, this.options.catalog.providers.find((provider) => provider.service === action.service)?.displayName)
|
|
64
75
|
: undefined;
|
|
65
76
|
input.signal?.throwIfAborted();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
const saasReference = connection.kind === "saas" ? connection.reference : undefined;
|
|
78
|
+
result = await executeProviderAction(action, saasReference
|
|
79
|
+
? async (actionInput) => {
|
|
80
|
+
if (!this.options.saas)
|
|
81
|
+
throw new SaasError("oauth_source_unavailable", "SaaS execution is unavailable.", 503);
|
|
82
|
+
const remote = await this.options.saas.executeAction(saasReference, action.service, action.id, actionInput, input.signal);
|
|
83
|
+
remoteExecutionId = remote.executionId;
|
|
84
|
+
return { ok: true, output: remote.output };
|
|
85
|
+
}
|
|
86
|
+
: connection.kind === "marketplace"
|
|
87
|
+
? (actionInput) => this.options.marketplace.execute(action.id, actionInput, input.signal)
|
|
88
|
+
: executor, input.input, this.createExecutionContext(connection.kind === "local" ? connection.getCredential : async () => undefined, input.signal));
|
|
69
89
|
if (input.signal?.aborted) {
|
|
70
90
|
result = cancelledExecutionResult();
|
|
71
91
|
}
|
|
@@ -82,6 +102,12 @@ export class ActionRunner {
|
|
|
82
102
|
policy = missingConnectionPolicy;
|
|
83
103
|
result = { ok: false, error: { code: policy.code, message: policy.message } };
|
|
84
104
|
}
|
|
105
|
+
else if (error instanceof SaasError) {
|
|
106
|
+
remoteExecutionId = error.remoteExecutionId;
|
|
107
|
+
failureStatus = error.status;
|
|
108
|
+
retryAfter = error.retryAfter;
|
|
109
|
+
result = { ok: false, error: { code: error.code, message: error.message } };
|
|
110
|
+
}
|
|
85
111
|
else {
|
|
86
112
|
result =
|
|
87
113
|
error instanceof ConnectionError
|
|
@@ -98,6 +124,7 @@ export class ActionRunner {
|
|
|
98
124
|
const auditError = safeRunLogError(result.error);
|
|
99
125
|
const runLog = {
|
|
100
126
|
id: executionId,
|
|
127
|
+
remoteExecutionId,
|
|
101
128
|
service: action.service,
|
|
102
129
|
actionId: input.actionId,
|
|
103
130
|
caller: input.caller,
|
|
@@ -126,6 +153,7 @@ export class ActionRunner {
|
|
|
126
153
|
}
|
|
127
154
|
const completedLogContext = {
|
|
128
155
|
...logContext,
|
|
156
|
+
remoteExecutionId,
|
|
129
157
|
connectionId: connection?.summary?.id,
|
|
130
158
|
durationMs,
|
|
131
159
|
ok: result.ok,
|
|
@@ -141,7 +169,15 @@ export class ActionRunner {
|
|
|
141
169
|
else {
|
|
142
170
|
this.options.logger?.warn(completedLogContext, "action run failed");
|
|
143
171
|
}
|
|
144
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
executionId,
|
|
174
|
+
remoteExecutionId,
|
|
175
|
+
failureStatus,
|
|
176
|
+
retryAfter,
|
|
177
|
+
auditPersisted,
|
|
178
|
+
result,
|
|
179
|
+
connection: connection?.summary,
|
|
180
|
+
};
|
|
145
181
|
}
|
|
146
182
|
listRuns(input) {
|
|
147
183
|
return this.options.runs.list(input);
|
|
@@ -21,6 +21,15 @@ const safeErrorMessages = {
|
|
|
21
21
|
oauth_token_expired: "The OAuth credential has expired.",
|
|
22
22
|
provider_error: "The provider request failed.",
|
|
23
23
|
rate_limited: "The provider rate limit was reached.",
|
|
24
|
+
execution_cancelled: "Action execution was cancelled; remote side effects may have completed.",
|
|
25
|
+
scope_missing: "The SaaS account is missing required scopes.",
|
|
26
|
+
credential_expired: "The SaaS account requires reauthorization.",
|
|
27
|
+
insufficient_credit: "Execution has insufficient credit.",
|
|
28
|
+
oauth_source_unavailable: "SaaS execution is unavailable.",
|
|
29
|
+
oauth_source_unauthorized: "The SaaS project key was rejected.",
|
|
30
|
+
oauth_source_protocol_error: "SaaS returned an incompatible response.",
|
|
31
|
+
oauth_source_unsupported: "The SaaS connection does not support this action.",
|
|
32
|
+
oauth_source_response_too_large: "The SaaS execution response exceeded the size limit.",
|
|
24
33
|
};
|
|
25
34
|
/** Return a bounded, redacted value suitable for run audit storage. */
|
|
26
35
|
export function summarizeForRunLog(value) {
|
package/src/server/api/auth.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface LocalAuthSession {
|
|
|
15
15
|
adminAuthConfigured: boolean;
|
|
16
16
|
authenticated: boolean;
|
|
17
17
|
}
|
|
18
|
+
export declare function hasAdminBearer(context: Context): boolean;
|
|
18
19
|
export declare function readRuntimeGrant(context: Context): RuntimeGrant | undefined;
|
|
19
20
|
export declare function createLocalAuthMiddleware(options: LocalAuthOptions): MiddlewareHandler;
|
|
20
21
|
export declare function readLocalAuthSession(context: Context, options: LocalAuthOptions): Promise<LocalAuthSession>;
|
package/src/server/api/auth.js
CHANGED
|
@@ -7,6 +7,10 @@ const authCookieName = "oomol_connect_admin_session";
|
|
|
7
7
|
const authCookieVersion = "v1";
|
|
8
8
|
const authCookieMaxAgeSeconds = 2_592_000;
|
|
9
9
|
const authCookieMaxAgeMs = authCookieMaxAgeSeconds * 1000;
|
|
10
|
+
const adminBearerRequests = new WeakSet();
|
|
11
|
+
export function hasAdminBearer(context) {
|
|
12
|
+
return adminBearerRequests.has(context.req.raw);
|
|
13
|
+
}
|
|
10
14
|
const runtimeGrants = new WeakMap();
|
|
11
15
|
export function readRuntimeGrant(context) {
|
|
12
16
|
return runtimeGrants.get(context.req.raw);
|
|
@@ -40,6 +44,8 @@ export function createLocalAuthMiddleware(options) {
|
|
|
40
44
|
message: "Configure an admin token to manage connections.",
|
|
41
45
|
});
|
|
42
46
|
}
|
|
47
|
+
if (adminToken && matchesConfiguredToken(context, adminToken))
|
|
48
|
+
adminBearerRequests.add(context.req.raw);
|
|
43
49
|
if (await hasValidToken(context, options, scope)) {
|
|
44
50
|
if (scope === "admin") {
|
|
45
51
|
await installAdminCookieForBearer(context, options);
|
|
@@ -82,6 +88,7 @@ async function installLocalAuthCookie(context, options) {
|
|
|
82
88
|
}
|
|
83
89
|
function isPublicPath(path, method) {
|
|
84
90
|
return (path === "/health" ||
|
|
91
|
+
(method === "GET" && path === "/oauth/saas/complete") ||
|
|
85
92
|
path === "/oauth/callback" ||
|
|
86
93
|
path.startsWith("/oauth/callback/") ||
|
|
87
94
|
(method === "GET" && path === "/api/auth/session") ||
|
|
@@ -137,7 +144,9 @@ async function hasValidToken(context, options, scope) {
|
|
|
137
144
|
return scope === "runtime" ? await hasValidRuntimeToken(context, options) : false;
|
|
138
145
|
}
|
|
139
146
|
async function hasRequestToken(context, token) {
|
|
140
|
-
|
|
147
|
+
if (context.req.header("authorization") !== undefined)
|
|
148
|
+
return matchesConfiguredToken(context, token);
|
|
149
|
+
return await hasValidAuthCookie(context, token);
|
|
141
150
|
}
|
|
142
151
|
async function hasValidAuthCookie(context, token) {
|
|
143
152
|
const cookie = getCookie(context, authCookieName);
|
|
@@ -239,5 +248,7 @@ function isConnectionManagementPath(path) {
|
|
|
239
248
|
return (/^\/v1\/providers\/[^/]+\/setup$/.test(path) ||
|
|
240
249
|
path === "/v1/connections" ||
|
|
241
250
|
path.startsWith("/v1/connections/") ||
|
|
242
|
-
path.startsWith("/v1/connection-requests/")
|
|
251
|
+
path.startsWith("/v1/connection-requests/") ||
|
|
252
|
+
path === "/api/oauth/connection-requests" ||
|
|
253
|
+
path.startsWith("/api/oauth/connection-requests/"));
|
|
243
254
|
}
|
|
@@ -8,7 +8,10 @@ export function getResponseCachePolicy(method, path, status) {
|
|
|
8
8
|
vary: "Authorization, Cookie, Accept-Encoding",
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
if (path.startsWith("/v1/connection-requests/")
|
|
11
|
+
if (path.startsWith("/v1/connection-requests/") ||
|
|
12
|
+
path === "/api/oauth/connection-requests" ||
|
|
13
|
+
path.startsWith("/api/oauth/connection-requests/") ||
|
|
14
|
+
path === "/oauth/saas/complete")
|
|
12
15
|
return { cacheControl: "private, no-store" };
|
|
13
16
|
if (isRuntimeResponsePath(path)) {
|
|
14
17
|
return { cacheControl: "no-store" };
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { OAuthConnectionRequestInput } from "../../oauth/oauth-flow-service.ts";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
export declare const oauthConnectionInput: z.ZodType<Omit<OAuthConnectionRequestInput, "owner" | "service" | "target">>;
|
|
3
|
+
export declare const oauthConnectionInput: z.ZodType<Omit<OAuthConnectionRequestInput, "owner" | "service" | "target" | "signal" | "connectionName">>;
|
|
4
|
+
export declare const consoleOAuthConnectionInput: z.ZodType<{
|
|
5
|
+
service: string;
|
|
6
|
+
connectionName: string;
|
|
7
|
+
appId?: string;
|
|
8
|
+
authorizationOptionIds?: string[];
|
|
9
|
+
}>;
|
|
4
10
|
export declare const apiKeyConnectionInput: z.ZodType<{
|
|
5
11
|
apiKey: string;
|
|
6
12
|
extra?: Record<string, string>;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const oauthConnectionInput = z.
|
|
2
|
+
export const oauthConnectionInput = z.strictObject({
|
|
3
3
|
returnUri: z.string().optional(),
|
|
4
4
|
authorizationOptionIds: z.array(z.string().trim().min(1)).optional(),
|
|
5
5
|
extra: z.record(z.string(), z.unknown()).optional(),
|
|
6
6
|
secretExtra: z.record(z.string(), z.string().trim().min(1)).optional(),
|
|
7
7
|
});
|
|
8
|
+
export const consoleOAuthConnectionInput = z.strictObject({
|
|
9
|
+
service: z.string().trim().min(1),
|
|
10
|
+
connectionName: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/),
|
|
11
|
+
appId: z.string().min(1).optional(),
|
|
12
|
+
authorizationOptionIds: z.array(z.string().trim().min(1)).optional(),
|
|
13
|
+
});
|
|
8
14
|
export const apiKeyConnectionInput = z
|
|
9
15
|
.object({
|
|
10
16
|
apiKey: z.string(),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ConnectionService } from "../../connection-service.ts";
|
|
2
2
|
import type { OAuthFlowService } from "../../oauth/oauth-flow-service.ts";
|
|
3
|
+
import type { SaasOAuthService } from "../../saas/saas-oauth-service.ts";
|
|
3
4
|
import { Hono } from "hono";
|
|
4
5
|
interface ConnectionRoutesOptions {
|
|
5
6
|
connections: ConnectionService;
|
|
6
7
|
oauthFlow: OAuthFlowService;
|
|
8
|
+
saasOAuth?: SaasOAuthService;
|
|
7
9
|
}
|
|
8
10
|
/** Personal connection management. Authentication runs in the parent app. */
|
|
9
|
-
export declare function createConnectionRoutes({ connections, oauthFlow }: ConnectionRoutesOptions): Hono;
|
|
11
|
+
export declare function createConnectionRoutes({ connections, oauthFlow, saasOAuth }: ConnectionRoutesOptions): Hono;
|
|
10
12
|
export {};
|
|
@@ -2,14 +2,26 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { ConnectionError } from "../../connection-service.js";
|
|
3
3
|
import { OAuthClientConfigError } from "../../oauth/oauth-client-config-service.js";
|
|
4
4
|
import { OAuthFlowError } from "../../oauth/oauth-flow-service.js";
|
|
5
|
+
import { SaasError } from "../../saas/saas-client.js";
|
|
6
|
+
import { hasAdminBearer } from "./auth.js";
|
|
5
7
|
import { readJsonBody, HttpRequestError } from "./http-utils.js";
|
|
6
8
|
import { connectionManagementFailure, serializeManagedConnection, writeRuntimeSuccess, writeRuntimeFailure, } from "./runtime-api.js";
|
|
7
9
|
/** Personal connection management. Authentication runs in the parent app. */
|
|
8
|
-
export function createConnectionRoutes({ connections, oauthFlow }) {
|
|
10
|
+
export function createConnectionRoutes({ connections, oauthFlow, saasOAuth }) {
|
|
9
11
|
const app = new Hono();
|
|
10
12
|
// The local runtime has one administrator principal, including its bearer and browser sessions.
|
|
11
13
|
const owner = "local-admin";
|
|
12
14
|
app.onError((error, context) => {
|
|
15
|
+
if (error instanceof SaasError) {
|
|
16
|
+
if (error.retryAfter)
|
|
17
|
+
context.header("Retry-After", error.retryAfter);
|
|
18
|
+
return writeRuntimeFailure(context, {
|
|
19
|
+
status: error.status,
|
|
20
|
+
errorCode: error.code,
|
|
21
|
+
message: error.message,
|
|
22
|
+
data: error.connectionRequestId ? { connectionRequestId: error.connectionRequestId } : undefined,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
13
25
|
if (error instanceof ConnectionError ||
|
|
14
26
|
error instanceof OAuthFlowError ||
|
|
15
27
|
error instanceof OAuthClientConfigError) {
|
|
@@ -27,7 +39,10 @@ export function createConnectionRoutes({ connections, oauthFlow }) {
|
|
|
27
39
|
return writeRuntimeSuccess(context, serializeManagedConnection(await connections.getManagedConnection(context.req.param("appId"))));
|
|
28
40
|
});
|
|
29
41
|
app.get("/connection-requests/:connectionRequestId", async (context) => {
|
|
30
|
-
const
|
|
42
|
+
const id = context.req.param("connectionRequestId");
|
|
43
|
+
const request = saasOAuth && hasAdminBearer(context)
|
|
44
|
+
? await saasOAuth.sync(id, owner, context.req.raw.signal)
|
|
45
|
+
: await oauthFlow.getConnectionRequest(id, owner);
|
|
31
46
|
if (!request)
|
|
32
47
|
return writeRuntimeFailure(context, {
|
|
33
48
|
status: 404,
|
|
@@ -46,6 +61,7 @@ export function createConnectionRoutes({ connections, oauthFlow }) {
|
|
|
46
61
|
...input,
|
|
47
62
|
service: target?.service ?? context.req.param("service"),
|
|
48
63
|
owner,
|
|
64
|
+
signal: context.req.raw.signal,
|
|
49
65
|
target,
|
|
50
66
|
}));
|
|
51
67
|
});
|
|
@@ -54,7 +70,9 @@ export function createConnectionRoutes({ connections, oauthFlow }) {
|
|
|
54
70
|
const { apiKeyConnectionInput, customConnectionInput } = await import("./connection-input.js");
|
|
55
71
|
const body = await readJsonBody(context);
|
|
56
72
|
const target = reconnect ? await connections.getStoredConnection(context.req.param("appId")) : undefined;
|
|
57
|
-
if (target &&
|
|
73
|
+
if (target &&
|
|
74
|
+
(target.source === "saas" ||
|
|
75
|
+
target.credential.authType !== (authType === "api-key" ? "api_key" : "custom_credential"))) {
|
|
58
76
|
throw new ConnectionError("unsupported_auth_type", "The connection uses a different credential type.");
|
|
59
77
|
}
|
|
60
78
|
const service = target?.service ?? context.req.param("service");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RuntimeStatus } from "./runtime-api.ts";
|
|
1
2
|
import type { Context } from "hono";
|
|
2
3
|
/**
|
|
3
4
|
* Loose JSON body shape accepted by local HTTP handlers.
|
|
@@ -22,7 +23,7 @@ export declare function readJsonBody(context: Context, maxBytes?: number): Promi
|
|
|
22
23
|
/**
|
|
23
24
|
* Write the standard JSON error envelope used by local HTTP routes.
|
|
24
25
|
*/
|
|
25
|
-
export declare function jsonError(context: Context, status:
|
|
26
|
+
export declare function jsonError(context: Context, status: RuntimeStatus, code: string, message: string): Response;
|
|
26
27
|
/**
|
|
27
28
|
* Write the standard not-found response.
|
|
28
29
|
*/
|
|
@@ -33,6 +34,6 @@ export declare function notFound(context: Context): Response;
|
|
|
33
34
|
export declare function internalError(context: Context, _error: unknown): Response;
|
|
34
35
|
export declare class HttpRequestError extends Error {
|
|
35
36
|
readonly code: string;
|
|
36
|
-
readonly status:
|
|
37
|
-
constructor(code: string, message: string, status?:
|
|
37
|
+
readonly status: RuntimeStatus;
|
|
38
|
+
constructor(code: string, message: string, status?: RuntimeStatus);
|
|
38
39
|
}
|