@agent-native/core 0.105.0 → 0.106.1
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +39 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/index.ts +2 -0
- package/corpus/core/src/application-state/script-helpers.ts +12 -0
- package/corpus/core/src/application-state/store.ts +38 -0
- package/corpus/core/src/client/AssistantChat.tsx +4 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +5 -1
- package/corpus/core/src/client/composer/PromptComposer.tsx +8 -0
- package/corpus/core/src/client/composer/TiptapComposer.tsx +38 -1
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +107 -32
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +33 -0
- package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
- package/corpus/core/src/client/use-chat-models.ts +10 -2
- package/corpus/core/src/connections/catalog.ts +107 -6
- package/corpus/core/src/localization/default-messages.ts +2 -0
- package/corpus/core/src/mcp/actions/call-mcp-tool.ts +18 -0
- package/corpus/core/src/mcp/actions/list-mcp-tools.ts +18 -0
- package/corpus/core/src/mcp-client/app-api.ts +136 -0
- package/corpus/core/src/mcp-client/index.ts +21 -0
- package/corpus/core/src/mcp-client/oauth-client.ts +462 -0
- package/corpus/core/src/mcp-client/oauth-routes.ts +307 -0
- package/corpus/core/src/mcp-client/remote-store.ts +93 -118
- package/corpus/core/src/mcp-client/remote-url.ts +97 -0
- package/corpus/core/src/mcp-client/routes.ts +11 -0
- package/corpus/core/src/oauth-tokens/store.ts +10 -4
- package/corpus/core/src/provider-api/index.ts +690 -55
- package/corpus/core/src/secrets/register-framework-secrets.ts +30 -0
- package/corpus/core/src/server/action-discovery.ts +2 -0
- package/corpus/core/src/server/core-routes-plugin.ts +9 -1
- package/corpus/core/src/server/workspace-provider-oauth.ts +448 -61
- package/corpus/core/src/templates/default/AGENTS.md +9 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
- package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -0
- package/corpus/templates/analytics/app/pages/DataSources.tsx +61 -0
- package/corpus/templates/analytics/server/handlers/jira.ts +7 -0
- package/corpus/templates/analytics/server/lib/jira.ts +14 -42
- package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +11 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +16 -0
- package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
- package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
- package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
- package/corpus/templates/design/actions/view-screen.ts +98 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
- package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
- package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
- package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
- package/corpus/templates/design/app/i18n-data.ts +32 -0
- package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
- package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
- package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +15 -0
- package/corpus/templates/design/shared/node-rewrite.ts +213 -0
- package/corpus/templates/design/shared/review-anchor.ts +26 -3
- package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/script-helpers.d.ts +1 -0
- package/dist/application-state/script-helpers.d.ts.map +1 -1
- package/dist/application-state/script-helpers.js +7 -1
- package/dist/application-state/script-helpers.js.map +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -0
- package/dist/application-state/store.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +2 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +5 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +2 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +5 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +4 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +13 -5
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +58 -14
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +13 -0
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/resources/use-mcp-servers.d.ts +1 -0
- package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
- package/dist/client/resources/use-mcp-servers.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/use-chat-models.d.ts +1 -0
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +8 -2
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +78 -7
- package/dist/connections/catalog.d.ts.map +1 -1
- package/dist/connections/catalog.js +97 -6
- package/dist/connections/catalog.js.map +1 -1
- package/dist/localization/default-messages.d.ts +2 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +2 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/actions/call-mcp-tool.d.ts +7 -0
- package/dist/mcp/actions/call-mcp-tool.d.ts.map +1 -0
- package/dist/mcp/actions/call-mcp-tool.js +15 -0
- package/dist/mcp/actions/call-mcp-tool.js.map +1 -0
- package/dist/mcp/actions/list-mcp-tools.d.ts +5 -0
- package/dist/mcp/actions/list-mcp-tools.d.ts.map +1 -0
- package/dist/mcp/actions/list-mcp-tools.js +16 -0
- package/dist/mcp/actions/list-mcp-tools.js.map +1 -0
- package/dist/mcp-client/app-api.d.ts +34 -0
- package/dist/mcp-client/app-api.d.ts.map +1 -0
- package/dist/mcp-client/app-api.js +94 -0
- package/dist/mcp-client/app-api.js.map +1 -0
- package/dist/mcp-client/index.d.ts +3 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +3 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/oauth-client.d.ts +113 -0
- package/dist/mcp-client/oauth-client.d.ts.map +1 -0
- package/dist/mcp-client/oauth-client.js +316 -0
- package/dist/mcp-client/oauth-client.js.map +1 -0
- package/dist/mcp-client/oauth-routes.d.ts +25 -0
- package/dist/mcp-client/oauth-routes.d.ts.map +1 -0
- package/dist/mcp-client/oauth-routes.js +218 -0
- package/dist/mcp-client/oauth-routes.js.map +1 -0
- package/dist/mcp-client/remote-store.d.ts +21 -6
- package/dist/mcp-client/remote-store.d.ts.map +1 -1
- package/dist/mcp-client/remote-store.js +72 -124
- package/dist/mcp-client/remote-store.js.map +1 -1
- package/dist/mcp-client/remote-url.d.ts +8 -0
- package/dist/mcp-client/remote-url.d.ts.map +1 -0
- package/dist/mcp-client/remote-url.js +103 -0
- package/dist/mcp-client/remote-url.js.map +1 -0
- package/dist/mcp-client/routes.d.ts +1 -0
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +9 -0
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/oauth-tokens/store.d.ts +2 -2
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +10 -6
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/index.d.ts +14 -0
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +499 -61
- package/dist/provider-api/index.js.map +1 -1
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +28 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +2 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +9 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/workspace-provider-oauth.d.ts +10 -3
- package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
- package/dist/server/workspace-provider-oauth.js +325 -43
- package/dist/server/workspace-provider-oauth.js.map +1 -1
- package/dist/templates/default/AGENTS.md +9 -7
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
- package/package.json +2 -2
- package/src/templates/default/AGENTS.md +9 -7
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type H3Event } from "h3";
|
|
2
2
|
import { type WorkspaceConnectionProvider } from "../connections/catalog.js";
|
|
3
3
|
import { type OAuthStatePayload } from "./google-oauth.js";
|
|
4
|
-
export type GenericWorkspaceOAuthProvider = "figma" | "google_drive" | "notion";
|
|
4
|
+
export type GenericWorkspaceOAuthProvider = "figma" | "google_drive" | "github" | "hubspot" | "jira" | "sentry" | "notion";
|
|
5
5
|
export interface WorkspaceProviderOAuthFlow {
|
|
6
6
|
provider: GenericWorkspaceOAuthProvider;
|
|
7
7
|
flowId: string;
|
|
@@ -32,10 +32,14 @@ export declare function exchangeWorkspaceProviderOAuthCode(input: {
|
|
|
32
32
|
verifier: string;
|
|
33
33
|
redirectUri: string;
|
|
34
34
|
}): Promise<Record<string, unknown>>;
|
|
35
|
-
export declare function resolveWorkspaceProviderIdentity(providerId: GenericWorkspaceOAuthProvider, tokens: Record<string, unknown>): Promise<
|
|
35
|
+
export declare function resolveWorkspaceProviderIdentity(providerId: GenericWorkspaceOAuthProvider, tokens: Record<string, unknown>): Promise<WorkspaceProviderIdentity>;
|
|
36
|
+
export declare function resolveWorkspaceProviderIdentities(providerId: GenericWorkspaceOAuthProvider, tokens: Record<string, unknown>): Promise<WorkspaceProviderIdentity[]>;
|
|
37
|
+
type WorkspaceProviderIdentity = {
|
|
36
38
|
accountId: string;
|
|
37
39
|
label: string;
|
|
38
|
-
|
|
40
|
+
scopes?: string[];
|
|
41
|
+
config?: Record<string, unknown>;
|
|
42
|
+
};
|
|
39
43
|
export declare function isWorkspaceProviderOAuthFlowValid(input: {
|
|
40
44
|
flow: WorkspaceProviderOAuthFlow;
|
|
41
45
|
state: OAuthStatePayload;
|
|
@@ -44,5 +48,8 @@ export declare function isWorkspaceProviderOAuthFlowValid(input: {
|
|
|
44
48
|
sessionOrgId?: string;
|
|
45
49
|
now?: number;
|
|
46
50
|
}): boolean;
|
|
51
|
+
export declare function canConnectWorkspaceProviderOAuth(orgId: string | null | undefined, role: string | null | undefined): boolean;
|
|
52
|
+
export declare function scopedOAuthAccountId(provider: GenericWorkspaceOAuthProvider, owner: string, accountId: string): string;
|
|
47
53
|
export declare function mergeWorkspaceOAuthValues(existing: readonly string[], incoming: readonly string[]): string[];
|
|
54
|
+
export {};
|
|
48
55
|
//# sourceMappingURL=workspace-provider-oauth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-provider-oauth.d.ts","sourceRoot":"","sources":["../../src/server/workspace-provider-oauth.ts"],"names":[],"mappings":"AAEA,OAAO,EAQL,KAAK,OAAO,EACb,MAAM,IAAI,CAAC;AAEZ,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace-provider-oauth.d.ts","sourceRoot":"","sources":["../../src/server/workspace-provider-oauth.ts"],"names":[],"mappings":"AAEA,OAAO,EAQL,KAAK,OAAO,EACb,MAAM,IAAI,CAAC;AAEZ,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,2BAA2B,CAAC;AAWnC,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,6BAA6B,GACrC,OAAO,GACP,cAAc,GACd,QAAQ,GACR,SAAS,GACT,MAAM,GACN,QAAQ,GACR,QAAQ,CAAC;AAeb,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,6BAA6B,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,6BAA6B,EACvC,KAAK,EAAE,OAAO,GAAG,UAAU,GAC1B,MAAM,CAER;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,6BAA6B,EACvC,KAAK,EAAE,OAAO,GAAG,UAAU,qHAU5B;AAED,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,6BAA6B,GACxC,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAgG7C;AAED,wBAAsB,oCAAoC,CACxD,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,6BAA6B,GACxC,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA2I7C;AAED,wBAAgB,sCAAsC,CAAC,KAAK,EAAE;IAC5D,QAAQ,EAAE,2BAA2B,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CA6BT;AAED,wBAAsB,kCAAkC,CAAC,KAAK,EAAE;IAC9D,UAAU,EAAE,6BAA6B,CAAC;IAC1C,QAAQ,EAAE,2BAA2B,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoJnC;AAED,wBAAsB,gCAAgC,CACpD,UAAU,EAAE,6BAA6B,EACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,yBAAyB,CAAC,CAWpC;AAED,wBAAsB,kCAAkC,CACtD,UAAU,EAAE,6BAA6B,EACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAwDtC;AAED,KAAK,yBAAyB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AA4JF,wBAAgB,iCAAiC,CAAC,KAAK,EAAE;IACvD,IAAI,EAAE,0BAA0B,CAAC;IACjC,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,6BAA6B,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAYV;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,OAAO,CAET;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,6BAA6B,EACvC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,CAQR;AA0ED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,MAAM,EAAE,CAEV"}
|
|
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import { deleteCookie, defineEventHandler, getCookie, getMethod, getQuery, setCookie, setResponseStatus, } from "h3";
|
|
3
3
|
import { getWorkspaceConnectionProvider, } from "../connections/catalog.js";
|
|
4
4
|
import { saveOAuthTokens, setOAuthDisplayName } from "../oauth-tokens/store.js";
|
|
5
|
+
import { getOrgContext } from "../org/context.js";
|
|
5
6
|
import { decryptSecretValue, encryptSecretValue } from "../secrets/crypto.js";
|
|
6
7
|
import { listWorkspaceConnections, upsertWorkspaceConnection, upsertWorkspaceConnectionGrant, } from "../workspace-connections/store.js";
|
|
7
8
|
import { getSession, safeReturnPath } from "./auth.js";
|
|
@@ -11,6 +12,10 @@ import { runWithRequestContext } from "./request-context.js";
|
|
|
11
12
|
const SUPPORTED_PROVIDERS = new Set([
|
|
12
13
|
"figma",
|
|
13
14
|
"google_drive",
|
|
15
|
+
"github",
|
|
16
|
+
"hubspot",
|
|
17
|
+
"jira",
|
|
18
|
+
"sentry",
|
|
14
19
|
"notion",
|
|
15
20
|
]);
|
|
16
21
|
const FLOW_TTL_SECONDS = 10 * 60;
|
|
@@ -33,6 +38,19 @@ export async function handleWorkspaceProviderOAuthStart(event, providerId) {
|
|
|
33
38
|
const session = await getSession(event).catch(() => null);
|
|
34
39
|
if (!session?.email)
|
|
35
40
|
return unauthorized(event);
|
|
41
|
+
const orgContext = await requireWorkspaceProviderOAuthAdmin(event);
|
|
42
|
+
if (!orgContext) {
|
|
43
|
+
return {
|
|
44
|
+
error: "Only organization owners and admins can connect shared OAuth accounts.",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const orgId = orgContext.orgId;
|
|
48
|
+
if (!orgId) {
|
|
49
|
+
setResponseStatus(event, 403);
|
|
50
|
+
return {
|
|
51
|
+
error: "Only organization owners and admins can connect shared OAuth accounts.",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
36
54
|
const provider = requiredProvider(providerId);
|
|
37
55
|
const query = getQuery(event);
|
|
38
56
|
const appId = normalizeAppId(text(query.appId) ??
|
|
@@ -44,11 +62,8 @@ export async function handleWorkspaceProviderOAuthStart(event, providerId) {
|
|
|
44
62
|
setResponseStatus(event, 400);
|
|
45
63
|
return { error: "Invalid OAuth redirect URI." };
|
|
46
64
|
}
|
|
47
|
-
return runWithRequestContext({ userEmail: session.email, orgId
|
|
48
|
-
const [clientId, clientSecret] = await
|
|
49
|
-
resolveSecret(clientCredentialKey(providerId, "id")),
|
|
50
|
-
resolveSecret(clientCredentialKey(providerId, "secret")),
|
|
51
|
-
]);
|
|
65
|
+
return runWithRequestContext({ userEmail: session.email, orgId }, async () => {
|
|
66
|
+
const [clientId, clientSecret] = await resolveProviderClientCredentials(providerId);
|
|
52
67
|
if (!clientId || !clientSecret) {
|
|
53
68
|
setResponseStatus(event, 503);
|
|
54
69
|
return {
|
|
@@ -68,7 +83,7 @@ export async function handleWorkspaceProviderOAuthStart(event, providerId) {
|
|
|
68
83
|
const state = encodeOAuthState({
|
|
69
84
|
redirectUri,
|
|
70
85
|
owner: session.email,
|
|
71
|
-
orgId
|
|
86
|
+
orgId,
|
|
72
87
|
app: appId,
|
|
73
88
|
returnUrl,
|
|
74
89
|
flowId,
|
|
@@ -79,7 +94,7 @@ export async function handleWorkspaceProviderOAuthStart(event, providerId) {
|
|
|
79
94
|
verifier,
|
|
80
95
|
redirectUri,
|
|
81
96
|
owner: session.email,
|
|
82
|
-
|
|
97
|
+
orgId,
|
|
83
98
|
appId,
|
|
84
99
|
expiresAt: Date.now() + FLOW_TTL_SECONDS * 1_000,
|
|
85
100
|
};
|
|
@@ -106,6 +121,19 @@ export async function handleWorkspaceProviderOAuthCallback(event, providerId) {
|
|
|
106
121
|
const session = await getSession(event).catch(() => null);
|
|
107
122
|
if (!session?.email)
|
|
108
123
|
return unauthorized(event);
|
|
124
|
+
const orgContext = await requireWorkspaceProviderOAuthAdmin(event);
|
|
125
|
+
if (!orgContext) {
|
|
126
|
+
return {
|
|
127
|
+
error: "Only organization owners and admins can connect shared OAuth accounts.",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const orgId = orgContext.orgId;
|
|
131
|
+
if (!orgId) {
|
|
132
|
+
setResponseStatus(event, 403);
|
|
133
|
+
return {
|
|
134
|
+
error: "Only organization owners and admins can connect shared OAuth accounts.",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
109
137
|
const query = getQuery(event);
|
|
110
138
|
const code = text(query.code);
|
|
111
139
|
const stateParam = text(query.state);
|
|
@@ -127,17 +155,14 @@ export async function handleWorkspaceProviderOAuthCallback(event, providerId) {
|
|
|
127
155
|
state,
|
|
128
156
|
provider: providerId,
|
|
129
157
|
sessionEmail: session.email,
|
|
130
|
-
sessionOrgId:
|
|
158
|
+
sessionOrgId: orgId,
|
|
131
159
|
})) {
|
|
132
160
|
setResponseStatus(event, 400);
|
|
133
161
|
return { error: "OAuth state is invalid or expired." };
|
|
134
162
|
}
|
|
135
163
|
const provider = requiredProvider(providerId);
|
|
136
|
-
return runWithRequestContext({ userEmail: session.email, orgId
|
|
137
|
-
const [clientId, clientSecret] = await
|
|
138
|
-
resolveSecret(clientCredentialKey(providerId, "id")),
|
|
139
|
-
resolveSecret(clientCredentialKey(providerId, "secret")),
|
|
140
|
-
]);
|
|
164
|
+
return runWithRequestContext({ userEmail: session.email, orgId }, async () => {
|
|
165
|
+
const [clientId, clientSecret] = await resolveProviderClientCredentials(providerId);
|
|
141
166
|
if (!clientId || !clientSecret) {
|
|
142
167
|
setResponseStatus(event, 503);
|
|
143
168
|
return {
|
|
@@ -153,34 +178,48 @@ export async function handleWorkspaceProviderOAuthCallback(event, providerId) {
|
|
|
153
178
|
verifier: flow.verifier,
|
|
154
179
|
redirectUri: flow.redirectUri,
|
|
155
180
|
});
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
const identities = await resolveWorkspaceProviderIdentities(providerId, tokens);
|
|
182
|
+
if (!identities.length) {
|
|
183
|
+
throw new Error(`${provider.label} OAuth did not return an accessible account.`);
|
|
184
|
+
}
|
|
185
|
+
const existingConnections = await listWorkspaceConnections({
|
|
160
186
|
provider: providerId,
|
|
161
187
|
includeDisabled: true,
|
|
162
|
-
})).find((connection) => connection.accountId === identity.accountId);
|
|
163
|
-
const scopes = mergeWorkspaceOAuthValues(existing?.scopes ?? [], tokenScopes(tokens, provider.oauth.scopes));
|
|
164
|
-
const connection = await upsertWorkspaceConnection({
|
|
165
|
-
...(existing ? { id: existing.id } : {}),
|
|
166
|
-
provider: providerId,
|
|
167
|
-
label: `${provider.label}: ${identity.label}`,
|
|
168
|
-
accountId: identity.accountId,
|
|
169
|
-
accountLabel: identity.label,
|
|
170
|
-
status: "connected",
|
|
171
|
-
scopes,
|
|
172
|
-
allowedApps: existing?.allowedApps ?? [],
|
|
173
|
-
config: { credentialMode: "oauth" },
|
|
174
|
-
lastCheckedAt: new Date(),
|
|
175
|
-
lastError: null,
|
|
176
|
-
});
|
|
177
|
-
await upsertWorkspaceConnectionGrant({
|
|
178
|
-
connectionId: connection.id,
|
|
179
|
-
appId: flow.appId,
|
|
180
|
-
provider: providerId,
|
|
181
|
-
scopes,
|
|
182
|
-
config: { credentialMode: "oauth" },
|
|
183
188
|
});
|
|
189
|
+
for (const identity of identities) {
|
|
190
|
+
const accountId = scopedOAuthAccountId(providerId, session.email, identity.accountId);
|
|
191
|
+
await saveOAuthTokens(provider.oauth.provider, accountId, tokens, session.email);
|
|
192
|
+
await setOAuthDisplayName(provider.oauth.provider, accountId, identity.label);
|
|
193
|
+
const existing = existingConnections.find((connection) => connection.accountId === accountId);
|
|
194
|
+
const scopes = mergeWorkspaceOAuthValues(existing?.scopes ?? [], identity.scopes ?? tokenScopes(tokens, provider.oauth.scopes));
|
|
195
|
+
const connectionConfig = {
|
|
196
|
+
credentialMode: "oauth",
|
|
197
|
+
...(providerId === "hubspot" || providerId === "jira"
|
|
198
|
+
? { externalAccountId: identity.accountId }
|
|
199
|
+
: {}),
|
|
200
|
+
...(identity.config ?? {}),
|
|
201
|
+
};
|
|
202
|
+
const connection = await upsertWorkspaceConnection({
|
|
203
|
+
...(existing ? { id: existing.id } : {}),
|
|
204
|
+
provider: providerId,
|
|
205
|
+
label: `${provider.label}: ${identity.label}`,
|
|
206
|
+
accountId,
|
|
207
|
+
accountLabel: identity.label,
|
|
208
|
+
status: "connected",
|
|
209
|
+
scopes,
|
|
210
|
+
allowedApps: existing?.allowedApps ?? [],
|
|
211
|
+
config: connectionConfig,
|
|
212
|
+
lastCheckedAt: new Date(),
|
|
213
|
+
lastError: null,
|
|
214
|
+
});
|
|
215
|
+
await upsertWorkspaceConnectionGrant({
|
|
216
|
+
connectionId: connection.id,
|
|
217
|
+
appId: flow.appId,
|
|
218
|
+
provider: providerId,
|
|
219
|
+
scopes,
|
|
220
|
+
config: connectionConfig,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
184
223
|
const returnPath = state.returnUrl ??
|
|
185
224
|
`/settings/connections?connected=${encodeURIComponent(providerId)}`;
|
|
186
225
|
return Response.redirect(getAppUrl(event, returnPath), 302);
|
|
@@ -194,10 +233,17 @@ export function buildWorkspaceProviderAuthorizationUrl(input) {
|
|
|
194
233
|
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
195
234
|
url.searchParams.set("response_type", "code");
|
|
196
235
|
url.searchParams.set("state", input.state);
|
|
197
|
-
if (input.provider.id === "figma") {
|
|
236
|
+
if (input.provider.id === "figma" || input.provider.id === "sentry") {
|
|
198
237
|
url.searchParams.set("code_challenge", input.challenge);
|
|
199
238
|
url.searchParams.set("code_challenge_method", "S256");
|
|
200
239
|
}
|
|
240
|
+
if (input.provider.id === "github") {
|
|
241
|
+
url.searchParams.set("allow_signup", "true");
|
|
242
|
+
}
|
|
243
|
+
if (input.provider.id === "jira") {
|
|
244
|
+
url.searchParams.set("audience", "api.atlassian.com");
|
|
245
|
+
url.searchParams.set("prompt", "consent");
|
|
246
|
+
}
|
|
201
247
|
if (input.provider.id === "google_drive") {
|
|
202
248
|
url.searchParams.set("access_type", "offline");
|
|
203
249
|
url.searchParams.set("include_granted_scopes", "true");
|
|
@@ -215,11 +261,91 @@ export async function exchangeWorkspaceProviderOAuthCode(input) {
|
|
|
215
261
|
if (!tokenUrl)
|
|
216
262
|
throw new Error("Provider does not support OAuth.");
|
|
217
263
|
const authorization = `Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString("base64")}`;
|
|
264
|
+
if (input.providerId === "github") {
|
|
265
|
+
const { response, body } = await fetchBoundedProviderJson(tokenUrl, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: {
|
|
268
|
+
Accept: "application/json",
|
|
269
|
+
"Content-Type": "application/json",
|
|
270
|
+
},
|
|
271
|
+
body: JSON.stringify({
|
|
272
|
+
client_id: input.clientId,
|
|
273
|
+
client_secret: input.clientSecret,
|
|
274
|
+
code: input.code,
|
|
275
|
+
redirect_uri: input.redirectUri,
|
|
276
|
+
}),
|
|
277
|
+
}, input.provider.label);
|
|
278
|
+
const accessToken = text(body.access_token);
|
|
279
|
+
if (!response.ok || !accessToken) {
|
|
280
|
+
throw new Error(`${input.provider.label} OAuth token exchange failed (${response.status}).`);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
...body,
|
|
284
|
+
...(Number.isFinite(Number(body.expires_in)) &&
|
|
285
|
+
Number(body.expires_in) > 0
|
|
286
|
+
? { expiry_date: Date.now() + Number(body.expires_in) * 1_000 }
|
|
287
|
+
: {}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (input.providerId === "hubspot") {
|
|
291
|
+
const { response, body } = await fetchBoundedProviderJson(tokenUrl, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
294
|
+
body: new URLSearchParams({
|
|
295
|
+
grant_type: "authorization_code",
|
|
296
|
+
client_id: input.clientId,
|
|
297
|
+
client_secret: input.clientSecret,
|
|
298
|
+
code: input.code,
|
|
299
|
+
redirect_uri: input.redirectUri,
|
|
300
|
+
}),
|
|
301
|
+
}, input.provider.label);
|
|
302
|
+
const accessToken = text(body.access_token);
|
|
303
|
+
if (!response.ok || !accessToken) {
|
|
304
|
+
throw new Error(`${input.provider.label} OAuth token exchange failed (${response.status}).`);
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
...body,
|
|
308
|
+
...(Number.isFinite(Number(body.expires_in)) &&
|
|
309
|
+
Number(body.expires_in) > 0
|
|
310
|
+
? { expiry_date: Date.now() + Number(body.expires_in) * 1_000 }
|
|
311
|
+
: {}),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (input.providerId === "jira") {
|
|
315
|
+
const { response, body } = await fetchBoundedProviderJsonValue(tokenUrl, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: { "Content-Type": "application/json" },
|
|
318
|
+
body: JSON.stringify({
|
|
319
|
+
grant_type: "authorization_code",
|
|
320
|
+
client_id: input.clientId,
|
|
321
|
+
client_secret: input.clientSecret,
|
|
322
|
+
code: input.code,
|
|
323
|
+
redirect_uri: input.redirectUri,
|
|
324
|
+
}),
|
|
325
|
+
}, input.provider.label);
|
|
326
|
+
const tokenBody = record(body) ?? {};
|
|
327
|
+
const accessToken = text(tokenBody.access_token);
|
|
328
|
+
if (!response.ok || !accessToken) {
|
|
329
|
+
throw new Error(`${input.provider.label} OAuth token exchange failed (${response.status}).`);
|
|
330
|
+
}
|
|
331
|
+
const expiresIn = Number(tokenBody.expires_in);
|
|
332
|
+
return {
|
|
333
|
+
...tokenBody,
|
|
334
|
+
...(Number.isFinite(expiresIn) && expiresIn > 0
|
|
335
|
+
? { expiry_date: Date.now() + expiresIn * 1_000 }
|
|
336
|
+
: {}),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
218
339
|
const fields = {
|
|
219
340
|
grant_type: "authorization_code",
|
|
220
341
|
code: input.code,
|
|
221
342
|
redirect_uri: input.redirectUri,
|
|
222
|
-
...(input.providerId === "figma"
|
|
343
|
+
...(input.providerId === "figma" || input.providerId === "sentry"
|
|
344
|
+
? { code_verifier: input.verifier }
|
|
345
|
+
: {}),
|
|
346
|
+
...(input.providerId === "sentry"
|
|
347
|
+
? { client_id: input.clientId, client_secret: input.clientSecret }
|
|
348
|
+
: {}),
|
|
223
349
|
...(input.providerId === "google_drive"
|
|
224
350
|
? { client_id: input.clientId, client_secret: input.clientSecret }
|
|
225
351
|
: {}),
|
|
@@ -255,6 +381,64 @@ export async function exchangeWorkspaceProviderOAuthCode(input) {
|
|
|
255
381
|
};
|
|
256
382
|
}
|
|
257
383
|
export async function resolveWorkspaceProviderIdentity(providerId, tokens) {
|
|
384
|
+
const [identity] = await resolveWorkspaceProviderIdentities(providerId, tokens);
|
|
385
|
+
if (!identity) {
|
|
386
|
+
throw new Error(`${providerId} OAuth response did not identify an account.`);
|
|
387
|
+
}
|
|
388
|
+
return identity;
|
|
389
|
+
}
|
|
390
|
+
export async function resolveWorkspaceProviderIdentities(providerId, tokens) {
|
|
391
|
+
if (providerId === "jira") {
|
|
392
|
+
const accessToken = text(tokens.access_token);
|
|
393
|
+
if (!accessToken) {
|
|
394
|
+
throw new Error("Jira OAuth response did not include an access token.");
|
|
395
|
+
}
|
|
396
|
+
const { response, body } = await fetchBoundedProviderJsonValue("https://api.atlassian.com/oauth/token/accessible-resources", {
|
|
397
|
+
headers: {
|
|
398
|
+
Accept: "application/json",
|
|
399
|
+
Authorization: `Bearer ${accessToken}`,
|
|
400
|
+
},
|
|
401
|
+
}, "Jira");
|
|
402
|
+
if (!response.ok || !Array.isArray(body)) {
|
|
403
|
+
throw new Error("Jira OAuth response did not identify accessible sites.");
|
|
404
|
+
}
|
|
405
|
+
return body.flatMap((resource) => {
|
|
406
|
+
const entry = record(resource);
|
|
407
|
+
const cloudId = text(entry?.id);
|
|
408
|
+
const siteUrl = text(entry?.url);
|
|
409
|
+
if (!cloudId || !siteUrl)
|
|
410
|
+
return [];
|
|
411
|
+
let parsedUrl;
|
|
412
|
+
try {
|
|
413
|
+
parsedUrl = new URL(siteUrl);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
if (parsedUrl.protocol !== "https:" ||
|
|
419
|
+
!parsedUrl.hostname.endsWith(".atlassian.net")) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
const scopes = Array.isArray(entry?.scopes)
|
|
423
|
+
? entry.scopes.filter((scope) => typeof scope === "string")
|
|
424
|
+
: undefined;
|
|
425
|
+
return [
|
|
426
|
+
{
|
|
427
|
+
accountId: cloudId,
|
|
428
|
+
label: text(entry?.name) ?? parsedUrl.hostname,
|
|
429
|
+
...(scopes?.length ? { scopes } : {}),
|
|
430
|
+
config: {
|
|
431
|
+
atlassianCloudId: cloudId,
|
|
432
|
+
atlassianApiBaseUrl: `https://api.atlassian.com/ex/jira/${encodeURIComponent(cloudId)}`,
|
|
433
|
+
siteUrl: parsedUrl.origin,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
];
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return [await resolveWorkspaceProviderIdentitySingle(providerId, tokens)];
|
|
440
|
+
}
|
|
441
|
+
async function resolveWorkspaceProviderIdentitySingle(providerId, tokens) {
|
|
258
442
|
if (providerId === "notion") {
|
|
259
443
|
const owner = record(tokens.owner);
|
|
260
444
|
const user = record(owner?.user);
|
|
@@ -272,6 +456,53 @@ export async function resolveWorkspaceProviderIdentity(providerId, tokens) {
|
|
|
272
456
|
"Notion workspace",
|
|
273
457
|
};
|
|
274
458
|
}
|
|
459
|
+
if (providerId === "github") {
|
|
460
|
+
const accessToken = text(tokens.access_token);
|
|
461
|
+
const { response, body: user } = await fetchBoundedProviderJson("https://api.github.com/user", {
|
|
462
|
+
headers: {
|
|
463
|
+
Accept: "application/vnd.github+json",
|
|
464
|
+
Authorization: `Bearer ${accessToken}`,
|
|
465
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
466
|
+
},
|
|
467
|
+
}, "GitHub");
|
|
468
|
+
const accountId = text(user.login) ?? text(user.id);
|
|
469
|
+
if (!response.ok || !accountId) {
|
|
470
|
+
throw new Error("GitHub OAuth response did not identify the connected user.");
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
accountId,
|
|
474
|
+
label: text(user.name) ?? text(user.login) ?? "GitHub account",
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
if (providerId === "hubspot") {
|
|
478
|
+
const accessToken = text(tokens.access_token);
|
|
479
|
+
const { response, body } = await fetchBoundedProviderJson(`https://api.hubapi.com/oauth/v1/access-tokens/${encodeURIComponent(accessToken)}`, { headers: { Accept: "application/json" } }, "HubSpot");
|
|
480
|
+
const accountId = scalarText(body.hub_id) ?? scalarText(body.user_id);
|
|
481
|
+
if (!response.ok || !accountId) {
|
|
482
|
+
throw new Error("HubSpot OAuth response did not identify the connected portal.");
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
accountId,
|
|
486
|
+
label: text(body.hub_domain) ?? `HubSpot portal ${accountId}`,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (providerId === "sentry") {
|
|
490
|
+
const accessToken = text(tokens.access_token);
|
|
491
|
+
const { response, body: user } = await fetchBoundedProviderJson("https://sentry.io/api/0/users/me/", {
|
|
492
|
+
headers: {
|
|
493
|
+
Accept: "application/json",
|
|
494
|
+
Authorization: `Bearer ${accessToken}`,
|
|
495
|
+
},
|
|
496
|
+
}, "Sentry");
|
|
497
|
+
const accountId = text(user.id);
|
|
498
|
+
if (!response.ok || !accountId) {
|
|
499
|
+
throw new Error("Sentry OAuth response did not identify the connected account.");
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
accountId,
|
|
503
|
+
label: text(user?.email) ?? text(user?.name) ?? "Sentry account",
|
|
504
|
+
};
|
|
505
|
+
}
|
|
275
506
|
if (providerId === "google_drive") {
|
|
276
507
|
const accessToken = text(tokens.access_token);
|
|
277
508
|
const { response, body } = await fetchBoundedProviderJson("https://www.googleapis.com/drive/v3/about?fields=user(displayName,emailAddress,permissionId)", { headers: { Authorization: `Bearer ${accessToken}` } }, "Google Drive");
|
|
@@ -326,7 +557,24 @@ export function isWorkspaceProviderOAuthFlowValid(input) {
|
|
|
326
557
|
input.sessionOrgId === input.flow.orgId &&
|
|
327
558
|
input.state.app === input.flow.appId);
|
|
328
559
|
}
|
|
560
|
+
export function canConnectWorkspaceProviderOAuth(orgId, role) {
|
|
561
|
+
return Boolean(orgId) && (role === "owner" || role === "admin");
|
|
562
|
+
}
|
|
563
|
+
export function scopedOAuthAccountId(provider, owner, accountId) {
|
|
564
|
+
if (provider !== "hubspot" && provider !== "jira")
|
|
565
|
+
return accountId;
|
|
566
|
+
const ownerKey = crypto
|
|
567
|
+
.createHash("sha256")
|
|
568
|
+
.update(`${provider}:${owner.trim().toLowerCase()}`)
|
|
569
|
+
.digest("hex")
|
|
570
|
+
.slice(0, 24);
|
|
571
|
+
return `${accountId}::${ownerKey}`;
|
|
572
|
+
}
|
|
329
573
|
async function fetchBoundedProviderJson(url, init, providerLabel) {
|
|
574
|
+
const { response, body } = await fetchBoundedProviderJsonValue(url, init, providerLabel);
|
|
575
|
+
return { response, body: record(body) ?? {} };
|
|
576
|
+
}
|
|
577
|
+
async function fetchBoundedProviderJsonValue(url, init, providerLabel) {
|
|
330
578
|
let response;
|
|
331
579
|
try {
|
|
332
580
|
response = await fetch(url, {
|
|
@@ -369,7 +617,7 @@ async function fetchBoundedProviderJson(url, init, providerLabel) {
|
|
|
369
617
|
try {
|
|
370
618
|
const json = Buffer.concat(chunks).toString("utf8");
|
|
371
619
|
const body = JSON.parse(json);
|
|
372
|
-
return { response, body
|
|
620
|
+
return { response, body };
|
|
373
621
|
}
|
|
374
622
|
catch {
|
|
375
623
|
return { response, body: {} };
|
|
@@ -382,9 +630,29 @@ function tokenScopes(tokens, fallback) {
|
|
|
382
630
|
export function mergeWorkspaceOAuthValues(existing, incoming) {
|
|
383
631
|
return [...new Set([...existing, ...incoming])];
|
|
384
632
|
}
|
|
385
|
-
function
|
|
633
|
+
function clientCredentialKeys(provider, field) {
|
|
386
634
|
const prefix = provider === "google_drive" ? "GOOGLE" : provider.toUpperCase();
|
|
387
|
-
|
|
635
|
+
const suffix = field === "id" ? "ID" : "SECRET";
|
|
636
|
+
if (provider === "github") {
|
|
637
|
+
const integrationPrefix = `GITHUB_INTEGRATION_CLIENT_${suffix}`;
|
|
638
|
+
return [integrationPrefix, `GITHUB_CLIENT_${suffix}`];
|
|
639
|
+
}
|
|
640
|
+
if (provider === "hubspot") {
|
|
641
|
+
const integrationPrefix = `HUBSPOT_INTEGRATION_CLIENT_${suffix}`;
|
|
642
|
+
return [integrationPrefix, `HUBSPOT_CLIENT_${suffix}`];
|
|
643
|
+
}
|
|
644
|
+
return [`${prefix}_CLIENT_${suffix}`];
|
|
645
|
+
}
|
|
646
|
+
async function resolveProviderClientCredentials(provider) {
|
|
647
|
+
const [clientId, clientSecret] = await Promise.all(["id", "secret"].map(async (field) => {
|
|
648
|
+
for (const key of clientCredentialKeys(provider, field)) {
|
|
649
|
+
const value = await resolveSecret(key);
|
|
650
|
+
if (value)
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
654
|
+
}));
|
|
655
|
+
return [clientId, clientSecret];
|
|
388
656
|
}
|
|
389
657
|
function flowCookieName(provider) {
|
|
390
658
|
return `an_workspace_oauth_${provider}`;
|
|
@@ -399,6 +667,11 @@ function normalizeAppId(value) {
|
|
|
399
667
|
function text(value) {
|
|
400
668
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
401
669
|
}
|
|
670
|
+
function scalarText(value) {
|
|
671
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
672
|
+
return String(value);
|
|
673
|
+
return text(value);
|
|
674
|
+
}
|
|
402
675
|
function record(value) {
|
|
403
676
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
404
677
|
? value
|
|
@@ -412,4 +685,13 @@ function unauthorized(event) {
|
|
|
412
685
|
setResponseStatus(event, 401);
|
|
413
686
|
return { error: "Authentication required" };
|
|
414
687
|
}
|
|
688
|
+
async function requireWorkspaceProviderOAuthAdmin(event) {
|
|
689
|
+
const context = await getOrgContext(event).catch(() => null);
|
|
690
|
+
if (!context ||
|
|
691
|
+
!canConnectWorkspaceProviderOAuth(context.orgId, context.role)) {
|
|
692
|
+
setResponseStatus(event, 403);
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
return context;
|
|
696
|
+
}
|
|
415
697
|
//# sourceMappingURL=workspace-provider-oauth.js.map
|