336 lines · 12.1 KB · TypeScript
RawDownload
import { FIGMA_SUPPORTED_OAUTH_SCOPES } from "@executor-js/plugin-openapi/presets";import { googleCatalogOAuthScopesForPreset } from "@executor-js/plugin-openapi/providers/google";import {  MICROSOFT_AUTHORIZATION_URL,  MICROSOFT_TOKEN_URL,} from "@executor-js/plugin-openapi/providers/microsoft";import { slackMcpUserScopes } from "@executor-js/react/lib/slack-mcp-oauth";import { IntegrationSlug, type FirstPartyOAuthClientConfig } from "@executor-js/sdk"; /** Cloud secret bindings that enable host-operated OAuth clients. A provider *  is absent unless both values in its pair are present. */export interface FirstPartyOAuthClientEnv {  readonly FIRST_PARTY_AIRTABLE_CLIENT_ID?: string;  readonly FIRST_PARTY_AIRTABLE_CLIENT_SECRET?: string;  readonly FIRST_PARTY_ATLASSIAN_CLIENT_ID?: string;  readonly FIRST_PARTY_ATLASSIAN_CLIENT_SECRET?: string;  readonly FIRST_PARTY_BOX_CLIENT_ID?: string;  readonly FIRST_PARTY_BOX_CLIENT_SECRET?: string;  readonly FIRST_PARTY_CLICKUP_CLIENT_ID?: string;  readonly FIRST_PARTY_CLICKUP_CLIENT_SECRET?: string;  readonly FIRST_PARTY_FIGMA_CLIENT_ID?: string;  readonly FIRST_PARTY_FIGMA_CLIENT_SECRET?: string;  readonly FIRST_PARTY_GITHUB_CLIENT_ID?: string;  readonly FIRST_PARTY_GITHUB_CLIENT_SECRET?: string;  readonly FIRST_PARTY_GITHUB_AUTHORIZE_URL?: string;  readonly FIRST_PARTY_GITHUB_TOKEN_URL?: string;  readonly FIRST_PARTY_GITLAB_CLIENT_ID?: string;  readonly FIRST_PARTY_GITLAB_CLIENT_SECRET?: string;  readonly FIRST_PARTY_GOOGLE_CLIENT_ID?: string;  readonly FIRST_PARTY_GOOGLE_CLIENT_SECRET?: string;  readonly FIRST_PARTY_HUBSPOT_CLIENT_ID?: string;  readonly FIRST_PARTY_HUBSPOT_CLIENT_SECRET?: string;  readonly FIRST_PARTY_LINEAR_CLIENT_ID?: string;  readonly FIRST_PARTY_LINEAR_CLIENT_SECRET?: string;  readonly FIRST_PARTY_MICROSOFT_CLIENT_ID?: string;  readonly FIRST_PARTY_MICROSOFT_CLIENT_SECRET?: string;  readonly FIRST_PARTY_NOTION_CLIENT_ID?: string;  readonly FIRST_PARTY_NOTION_CLIENT_SECRET?: string;  readonly FIRST_PARTY_SLACK_CLIENT_ID?: string;  readonly FIRST_PARTY_SLACK_CLIENT_SECRET?: string;} const AIRTABLE_SCOPES = [  "data.recordComments:read",  "data.recordComments:write",  "data.records:read",  "data.records:write",  "data.records:manage",  "schema.bases:read",  "schema.bases:write",  "user.email:read",  "workspacesAndBases:read",  "workspacesAndBases:write",  "workspacesAndBases.shares:manage",  "webhook:manage",] as const; const ATLASSIAN_SCOPES = [  "read:me",  "read:account",  "read:jira-work",  "manage:jira-project",  "manage:jira-configuration",  "read:jira-user",  "write:jira-work",  "manage:jira-webhook",  "read:servicedesk-request",  "manage:servicedesk-customer",  "write:servicedesk-request",  "write:confluence-content",  "read:confluence-space.summary",  "write:confluence-space",  "write:confluence-file",  "read:confluence-props",  "write:confluence-props",  "manage:confluence-configuration",  "read:confluence-content.all",  "read:confluence-content.summary",  "search:confluence",  "read:confluence-content.permission",  "read:confluence-user",  "read:confluence-groups",  "write:confluence-groups",  "offline_access",] as const; const BOX_SCOPES = [  "root_readonly",  "root_readwrite",  "sign_requests.readwrite",  "ai.readwrite",  "manage_webhook",  "manage_triggers",] as const; const GITLAB_SCOPES = [  "api",  "read_api",  "read_user",  "create_runner",  "manage_runner",  "k8s_proxy",  "mcp",  "mcp_orbit",  "read_repository",  "write_repository",  "read_registry",  "write_registry",  "read_virtual_registry",  "write_virtual_registry",  "read_observability",  "write_observability",  "ai_features",  "openid",  "profile",  "email",] as const; const HUBSPOT_REQUIRED_SCOPES = [  "oauth",  "account-info.security.read",  "cms.domains.read",  "cms.domains.write",  "crm.export",  "crm.import",  "crm.lists.read",  "crm.lists.write",  "crm.objects.companies.read",  "crm.objects.companies.write",  "crm.objects.contacts.read",  "crm.objects.contacts.write",  "crm.objects.deals.read",  "crm.objects.deals.write",  "crm.objects.marketing_events.read",  "crm.objects.marketing_events.write",  "crm.objects.owners.read",  "crm.objects.quotes.read",  "crm.objects.quotes.write",  "crm.schemas.companies.read",  "crm.schemas.companies.write",  "crm.schemas.contacts.read",  "crm.schemas.contacts.write",  "sales-email-read",  "settings.users.read",  "settings.users.write",  "tickets",  "timeline",] as const; const HUBSPOT_OPTIONAL_SCOPES = [  "content",  "crm.objects.custom.read",  "crm.schemas.custom.read",] as const; const MICROSOFT_SCOPES = [  "User.Read",  "Calendars.ReadWrite",  "Channel.ReadBasic.All",  "ChannelMessage.Read.All",  "ChannelMessage.Send",  "Chat.ReadWrite",  "Files.ReadWrite.All",  "Mail.ReadWrite",  "Mail.Send",  "MailboxSettings.ReadWrite",  "OnlineMeetings.ReadWrite",  "Sites.ReadWrite.All",  "Team.ReadBasic.All",  "offline_access",] as const; // Consumer Google launch boundary. Keep this list aligned with the scopes// submitted for the Executor-owned production app: ordinary Workspace services// plus Photos, Meet, and Search Console. Admin, Classroom, YouTube, Apps Script,// BigQuery, and Cloud Resource Manager have materially different audiences or// provider requirements and remain BYO OAuth. The same scope source builds each// catalog auth template, preventing picker/start drift.const GOOGLE_FIRST_PARTY_PRESET_IDS = [  "google-calendar",  "google-meet",  "google-gmail",  "google-sheets",  "google-drive",  "google-docs",  "google-slides",  "google-forms",  "google-tasks",  "google-people",  "google-photos-library",  "google-photos-picker",  "google-search-console",] as const; const GOOGLE_ALLOWED_SCOPES: readonly string[] = [  ...new Set([    ...GOOGLE_FIRST_PARTY_PRESET_IDS.flatMap(googleCatalogOAuthScopesForPreset),    // Connections created before the full-Gmail review retain this declared    // scope on reconnect. New Gmail presets request `mail.google.com`.    "https://www.googleapis.com/auth/gmail.modify",  ]),]; const client = (  clientId: string | undefined,  clientSecret: string | undefined,  config: Omit<FirstPartyOAuthClientConfig, "clientId" | "clientSecret">,): readonly FirstPartyOAuthClientConfig[] =>  clientId && clientSecret ? [{ ...config, clientId, clientSecret }] : []; /** Build the enabled first-party registry from secret bindings. Provider *  protocol details and scope ceilings live here so the cloud composition root *  cannot drift from the registered production clients. * *  Each provider-side registration must list *  `${VITE_PUBLIC_SITE_URL}/api/oauth/callback` as its callback; the org slug *  travels inside OAuth `state`, so the single static callback serves every *  org. * *  The endpoint URLs default to the real provider; the `_AUTHORIZE_URL` / *  `_TOKEN_URL` overrides exist so tests and dev instances can point the app at *  an emulated provider (`@executor-js/emulate`) and run the complete flow. *  Production leaves them unset. */export const firstPartyOAuthClientsFor = (  env: FirstPartyOAuthClientEnv,): readonly FirstPartyOAuthClientConfig[] => [  ...client(env.FIRST_PARTY_AIRTABLE_CLIENT_ID, env.FIRST_PARTY_AIRTABLE_CLIENT_SECRET, {    name: "airtable",    authorizationUrl: "https://airtable.com/oauth2/v1/authorize",    tokenUrl: "https://airtable.com/oauth2/v1/token",    tokenEndpointAuthMethod: "basic",    authorizationScopes: AIRTABLE_SCOPES,    allowedScopes: AIRTABLE_SCOPES,  }),  ...client(env.FIRST_PARTY_ATLASSIAN_CLIENT_ID, env.FIRST_PARTY_ATLASSIAN_CLIENT_SECRET, {    name: "atlassian",    authorizationUrl: "https://auth.atlassian.com/authorize",    tokenUrl: "https://auth.atlassian.com/oauth/token",    tokenRequestFormat: "json",    authorizationScopes: ATLASSIAN_SCOPES,    allowedScopes: ATLASSIAN_SCOPES,    authorizationExtraParams: { audience: "api.atlassian.com", prompt: "consent" },  }),  ...client(env.FIRST_PARTY_BOX_CLIENT_ID, env.FIRST_PARTY_BOX_CLIENT_SECRET, {    name: "box",    authorizationUrl: "https://account.box.com/api/oauth2/authorize",    tokenUrl: "https://api.box.com/oauth2/token",    authorizationScopes: BOX_SCOPES,    allowedScopes: BOX_SCOPES,  }),  ...client(env.FIRST_PARTY_CLICKUP_CLIENT_ID, env.FIRST_PARTY_CLICKUP_CLIENT_SECRET, {    name: "clickup",    authorizationUrl: "https://app.clickup.com/api",    tokenUrl: "https://api.clickup.com/api/v2/oauth/token",    tokenRequestFormat: "json",    authorizationScopes: [],  }),  ...client(env.FIRST_PARTY_FIGMA_CLIENT_ID, env.FIRST_PARTY_FIGMA_CLIENT_SECRET, {    name: "figma",    authorizationUrl: "https://www.figma.com/oauth",    tokenUrl: "https://api.figma.com/v1/oauth/token",    tokenEndpointAuthMethod: "basic",    integrations: [IntegrationSlug.make("figma_api")],    authorizationScopes: FIGMA_SUPPORTED_OAUTH_SCOPES,    allowedScopes: FIGMA_SUPPORTED_OAUTH_SCOPES,  }),  ...client(env.FIRST_PARTY_GITHUB_CLIENT_ID, env.FIRST_PARTY_GITHUB_CLIENT_SECRET, {    name: "github",    authorizationUrl:      env.FIRST_PARTY_GITHUB_AUTHORIZE_URL ?? "https://github.com/login/oauth/authorize",    tokenUrl: env.FIRST_PARTY_GITHUB_TOKEN_URL ?? "https://github.com/login/oauth/access_token",    integrations: [IntegrationSlug.make("github_rest")],    // GitHub App user access tokens do not use classic OAuth scopes; their    // capabilities come from the app's registered permissions.    authorizationScopes: [],  }),  ...client(env.FIRST_PARTY_GITLAB_CLIENT_ID, env.FIRST_PARTY_GITLAB_CLIENT_SECRET, {    name: "gitlab",    authorizationUrl: "https://gitlab.com/oauth/authorize",    tokenUrl: "https://gitlab.com/oauth/token",    authorizationScopes: GITLAB_SCOPES,    allowedScopes: GITLAB_SCOPES,  }),  ...client(env.FIRST_PARTY_GOOGLE_CLIENT_ID, env.FIRST_PARTY_GOOGLE_CLIENT_SECRET, {    name: "google",    authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",    tokenUrl: "https://oauth2.googleapis.com/token",    allowedScopes: GOOGLE_ALLOWED_SCOPES,    // Withdrawn from the connect picker: no new connection is offered the    // Executor-owned Google app. The entry stays declared on purpose — every    // connection already minted against it keeps refreshing and reconnecting    // through it. Deleting this block, or unsetting the env vars, would strand    // those connections instead.    unlisted: true,  }),  ...client(env.FIRST_PARTY_HUBSPOT_CLIENT_ID, env.FIRST_PARTY_HUBSPOT_CLIENT_SECRET, {    name: "hubspot",    authorizationUrl: "https://app.hubspot.com/oauth/authorize",    tokenUrl: "https://api.hubapi.com/oauth/v3/token",    authorizationScopes: HUBSPOT_REQUIRED_SCOPES,    allowedScopes: [...HUBSPOT_REQUIRED_SCOPES, ...HUBSPOT_OPTIONAL_SCOPES],    authorizationExtraParams: { optional_scope: HUBSPOT_OPTIONAL_SCOPES.join(" ") },  }),  ...client(env.FIRST_PARTY_LINEAR_CLIENT_ID, env.FIRST_PARTY_LINEAR_CLIENT_SECRET, {    name: "linear",    authorizationUrl: "https://linear.app/oauth/authorize",    tokenUrl: "https://api.linear.app/oauth/token",    authorizationScopes: ["read", "write"],    authorizationScopeSeparator: ",",    allowedScopes: ["read", "write"],  }),  ...client(env.FIRST_PARTY_MICROSOFT_CLIENT_ID, env.FIRST_PARTY_MICROSOFT_CLIENT_SECRET, {    name: "microsoft",    authorizationUrl: MICROSOFT_AUTHORIZATION_URL,    tokenUrl: MICROSOFT_TOKEN_URL,    allowedScopes: MICROSOFT_SCOPES,    additionalAuthorizationScopes: ["offline_access"],  }),  ...client(env.FIRST_PARTY_NOTION_CLIENT_ID, env.FIRST_PARTY_NOTION_CLIENT_SECRET, {    name: "notion",    authorizationUrl: "https://api.notion.com/v1/oauth/authorize",    tokenUrl: "https://api.notion.com/v1/oauth/token",    authorizationExtraParams: { owner: "user" },    tokenEndpointAuthMethod: "basic",    tokenRequestFormat: "json",    authorizationScopes: [],  }),  ...client(env.FIRST_PARTY_SLACK_CLIENT_ID, env.FIRST_PARTY_SLACK_CLIENT_SECRET, {    name: "slack",    authorizationUrl: "https://slack.com/oauth/v2_user/authorize",    tokenUrl: "https://slack.com/api/oauth.v2.user.access",    resource: "https://mcp.slack.com",    integrations: [IntegrationSlug.make("slack")],    allowedScopes: slackMcpUserScopes,  }),];