@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.
Files changed (194) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +39 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/index.ts +2 -0
  5. package/corpus/core/src/application-state/script-helpers.ts +12 -0
  6. package/corpus/core/src/application-state/store.ts +38 -0
  7. package/corpus/core/src/client/AssistantChat.tsx +4 -0
  8. package/corpus/core/src/client/MultiTabAssistantChat.tsx +5 -1
  9. package/corpus/core/src/client/composer/PromptComposer.tsx +8 -0
  10. package/corpus/core/src/client/composer/TiptapComposer.tsx +38 -1
  11. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +107 -32
  12. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +33 -0
  13. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  14. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
  15. package/corpus/core/src/client/use-chat-models.ts +10 -2
  16. package/corpus/core/src/connections/catalog.ts +107 -6
  17. package/corpus/core/src/localization/default-messages.ts +2 -0
  18. package/corpus/core/src/mcp/actions/call-mcp-tool.ts +18 -0
  19. package/corpus/core/src/mcp/actions/list-mcp-tools.ts +18 -0
  20. package/corpus/core/src/mcp-client/app-api.ts +136 -0
  21. package/corpus/core/src/mcp-client/index.ts +21 -0
  22. package/corpus/core/src/mcp-client/oauth-client.ts +462 -0
  23. package/corpus/core/src/mcp-client/oauth-routes.ts +307 -0
  24. package/corpus/core/src/mcp-client/remote-store.ts +93 -118
  25. package/corpus/core/src/mcp-client/remote-url.ts +97 -0
  26. package/corpus/core/src/mcp-client/routes.ts +11 -0
  27. package/corpus/core/src/oauth-tokens/store.ts +10 -4
  28. package/corpus/core/src/provider-api/index.ts +690 -55
  29. package/corpus/core/src/secrets/register-framework-secrets.ts +30 -0
  30. package/corpus/core/src/server/action-discovery.ts +2 -0
  31. package/corpus/core/src/server/core-routes-plugin.ts +9 -1
  32. package/corpus/core/src/server/workspace-provider-oauth.ts +448 -61
  33. package/corpus/core/src/templates/default/AGENTS.md +9 -7
  34. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  35. package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -0
  36. package/corpus/templates/analytics/app/pages/DataSources.tsx +61 -0
  37. package/corpus/templates/analytics/server/handlers/jira.ts +7 -0
  38. package/corpus/templates/analytics/server/lib/jira.ts +14 -42
  39. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +11 -0
  40. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
  41. package/corpus/templates/design/AGENTS.md +16 -0
  42. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
  43. package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
  44. package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
  45. package/corpus/templates/design/actions/view-screen.ts +98 -2
  46. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
  47. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
  48. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
  49. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
  50. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
  51. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
  52. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
  53. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
  54. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
  55. package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
  56. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
  57. package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
  58. package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
  59. package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
  60. package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
  61. package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
  62. package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
  63. package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
  64. package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
  65. package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
  66. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  67. package/corpus/templates/design/app/i18n-data.ts +32 -0
  68. package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
  69. package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
  70. package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
  71. package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
  72. package/corpus/templates/design/shared/code-layer.ts +15 -0
  73. package/corpus/templates/design/shared/node-rewrite.ts +213 -0
  74. package/corpus/templates/design/shared/review-anchor.ts +26 -3
  75. package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
  76. package/dist/application-state/index.d.ts +2 -2
  77. package/dist/application-state/index.d.ts.map +1 -1
  78. package/dist/application-state/index.js +2 -2
  79. package/dist/application-state/index.js.map +1 -1
  80. package/dist/application-state/script-helpers.d.ts +1 -0
  81. package/dist/application-state/script-helpers.d.ts.map +1 -1
  82. package/dist/application-state/script-helpers.js +7 -1
  83. package/dist/application-state/script-helpers.js.map +1 -1
  84. package/dist/application-state/store.d.ts +1 -0
  85. package/dist/application-state/store.d.ts.map +1 -1
  86. package/dist/application-state/store.js +28 -0
  87. package/dist/application-state/store.js.map +1 -1
  88. package/dist/client/AssistantChat.d.ts +2 -0
  89. package/dist/client/AssistantChat.d.ts.map +1 -1
  90. package/dist/client/AssistantChat.js +2 -2
  91. package/dist/client/AssistantChat.js.map +1 -1
  92. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  93. package/dist/client/MultiTabAssistantChat.js +5 -2
  94. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  95. package/dist/client/composer/PromptComposer.d.ts +2 -0
  96. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  97. package/dist/client/composer/PromptComposer.js +5 -2
  98. package/dist/client/composer/PromptComposer.js.map +1 -1
  99. package/dist/client/composer/TiptapComposer.d.ts +4 -1
  100. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  101. package/dist/client/composer/TiptapComposer.js +13 -5
  102. package/dist/client/composer/TiptapComposer.js.map +1 -1
  103. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  104. package/dist/client/resources/McpIntegrationDialog.js +58 -14
  105. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  106. package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
  107. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  108. package/dist/client/resources/mcp-integration-catalog.js +13 -0
  109. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  110. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  111. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  112. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  113. package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
  114. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  115. package/dist/client/review/ReviewCommentComposer.js +3 -3
  116. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  117. package/dist/client/use-chat-models.d.ts +1 -0
  118. package/dist/client/use-chat-models.d.ts.map +1 -1
  119. package/dist/client/use-chat-models.js +8 -2
  120. package/dist/client/use-chat-models.js.map +1 -1
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/connections/catalog.d.ts +78 -7
  123. package/dist/connections/catalog.d.ts.map +1 -1
  124. package/dist/connections/catalog.js +97 -6
  125. package/dist/connections/catalog.js.map +1 -1
  126. package/dist/localization/default-messages.d.ts +2 -0
  127. package/dist/localization/default-messages.d.ts.map +1 -1
  128. package/dist/localization/default-messages.js +2 -0
  129. package/dist/localization/default-messages.js.map +1 -1
  130. package/dist/mcp/actions/call-mcp-tool.d.ts +7 -0
  131. package/dist/mcp/actions/call-mcp-tool.d.ts.map +1 -0
  132. package/dist/mcp/actions/call-mcp-tool.js +15 -0
  133. package/dist/mcp/actions/call-mcp-tool.js.map +1 -0
  134. package/dist/mcp/actions/list-mcp-tools.d.ts +5 -0
  135. package/dist/mcp/actions/list-mcp-tools.d.ts.map +1 -0
  136. package/dist/mcp/actions/list-mcp-tools.js +16 -0
  137. package/dist/mcp/actions/list-mcp-tools.js.map +1 -0
  138. package/dist/mcp-client/app-api.d.ts +34 -0
  139. package/dist/mcp-client/app-api.d.ts.map +1 -0
  140. package/dist/mcp-client/app-api.js +94 -0
  141. package/dist/mcp-client/app-api.js.map +1 -0
  142. package/dist/mcp-client/index.d.ts +3 -1
  143. package/dist/mcp-client/index.d.ts.map +1 -1
  144. package/dist/mcp-client/index.js +3 -1
  145. package/dist/mcp-client/index.js.map +1 -1
  146. package/dist/mcp-client/oauth-client.d.ts +113 -0
  147. package/dist/mcp-client/oauth-client.d.ts.map +1 -0
  148. package/dist/mcp-client/oauth-client.js +316 -0
  149. package/dist/mcp-client/oauth-client.js.map +1 -0
  150. package/dist/mcp-client/oauth-routes.d.ts +25 -0
  151. package/dist/mcp-client/oauth-routes.d.ts.map +1 -0
  152. package/dist/mcp-client/oauth-routes.js +218 -0
  153. package/dist/mcp-client/oauth-routes.js.map +1 -0
  154. package/dist/mcp-client/remote-store.d.ts +21 -6
  155. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  156. package/dist/mcp-client/remote-store.js +72 -124
  157. package/dist/mcp-client/remote-store.js.map +1 -1
  158. package/dist/mcp-client/remote-url.d.ts +8 -0
  159. package/dist/mcp-client/remote-url.d.ts.map +1 -0
  160. package/dist/mcp-client/remote-url.js +103 -0
  161. package/dist/mcp-client/remote-url.js.map +1 -0
  162. package/dist/mcp-client/routes.d.ts +1 -0
  163. package/dist/mcp-client/routes.d.ts.map +1 -1
  164. package/dist/mcp-client/routes.js +9 -0
  165. package/dist/mcp-client/routes.js.map +1 -1
  166. package/dist/notifications/routes.d.ts +2 -2
  167. package/dist/oauth-tokens/store.d.ts +2 -2
  168. package/dist/oauth-tokens/store.d.ts.map +1 -1
  169. package/dist/oauth-tokens/store.js +10 -6
  170. package/dist/oauth-tokens/store.js.map +1 -1
  171. package/dist/observability/routes.d.ts +5 -5
  172. package/dist/provider-api/index.d.ts +14 -0
  173. package/dist/provider-api/index.d.ts.map +1 -1
  174. package/dist/provider-api/index.js +499 -61
  175. package/dist/provider-api/index.js.map +1 -1
  176. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  177. package/dist/secrets/register-framework-secrets.js +28 -0
  178. package/dist/secrets/register-framework-secrets.js.map +1 -1
  179. package/dist/secrets/routes.d.ts +9 -9
  180. package/dist/server/action-discovery.d.ts.map +1 -1
  181. package/dist/server/action-discovery.js +2 -0
  182. package/dist/server/action-discovery.js.map +1 -1
  183. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  184. package/dist/server/core-routes-plugin.js +9 -1
  185. package/dist/server/core-routes-plugin.js.map +1 -1
  186. package/dist/server/workspace-provider-oauth.d.ts +10 -3
  187. package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
  188. package/dist/server/workspace-provider-oauth.js +325 -43
  189. package/dist/server/workspace-provider-oauth.js.map +1 -1
  190. package/dist/templates/default/AGENTS.md +9 -7
  191. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  192. package/package.json +2 -2
  193. package/src/templates/default/AGENTS.md +9 -7
  194. 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;AAUnC,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,6BAA6B,GAAG,OAAO,GAAG,cAAc,GAAG,QAAQ,CAAC;AAWhF,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,CAmF7C;AAED,wBAAsB,oCAAoC,CACxD,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,6BAA6B,GACxC,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0G7C;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,CAsBT;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,CAkDnC;AAED,wBAAsB,gCAAgC,CACpD,UAAU,EAAE,6BAA6B,EACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA4D/C;AA0BD,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;AA6DD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,MAAM,EAAE,CAEV"}
1
+ {"version":3,"file":"workspace-provider-oauth.d.ts","sourceRoot":"","sources":["../../src/server/workspace-provider-oauth.ts"],"names":[],"mappings":"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"}
@@ -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: session.orgId }, async () => {
48
- const [clientId, clientSecret] = await Promise.all([
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: session.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
- ...(session.orgId ? { orgId: session.orgId } : {}),
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: session.orgId,
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: session.orgId }, async () => {
137
- const [clientId, clientSecret] = await Promise.all([
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 identity = await resolveWorkspaceProviderIdentity(providerId, tokens);
157
- await saveOAuthTokens(provider.oauth.provider, identity.accountId, tokens, session.email);
158
- await setOAuthDisplayName(provider.oauth.provider, identity.accountId, identity.label);
159
- const existing = (await listWorkspaceConnections({
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" ? { code_verifier: input.verifier } : {}),
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: record(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 clientCredentialKey(provider, field) {
633
+ function clientCredentialKeys(provider, field) {
386
634
  const prefix = provider === "google_drive" ? "GOOGLE" : provider.toUpperCase();
387
- return `${prefix}_CLIENT_${field === "id" ? "ID" : "SECRET"}`;
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