@agent-native/core 0.104.0 → 0.106.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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +64 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- 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/use-chat-models.ts +10 -2
- package/corpus/core/src/connections/catalog.ts +107 -6
- package/corpus/core/src/deploy/build.ts +23 -0
- 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/clips/app/routes.ts +3 -1
- package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.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/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/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/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/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +17 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +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 +3 -3
- 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/progress/routes.d.ts +1 -1
- 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/resources/handlers.d.ts +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
|
@@ -320,8 +320,10 @@ const PROVIDER_CONFIGS = {
|
|
|
320
320
|
label: "GitHub REST API",
|
|
321
321
|
defaultBaseUrl: "https://api.github.com",
|
|
322
322
|
auth: {
|
|
323
|
-
type: "bearer",
|
|
324
|
-
|
|
323
|
+
type: "oauth-bearer-or-bearer-key",
|
|
324
|
+
oauthProvider: "github",
|
|
325
|
+
tokenLabel: "GitHub OAuth token",
|
|
326
|
+
key: "GITHUB_TOKEN",
|
|
325
327
|
workspaceProvider: "github",
|
|
326
328
|
},
|
|
327
329
|
credentialKeys: ["GITHUB_TOKEN"],
|
|
@@ -632,8 +634,11 @@ const PROVIDER_CONFIGS = {
|
|
|
632
634
|
label: "HubSpot",
|
|
633
635
|
defaultBaseUrl: "https://api.hubapi.com",
|
|
634
636
|
auth: {
|
|
635
|
-
type: "bearer",
|
|
636
|
-
|
|
637
|
+
type: "oauth-bearer-or-bearer-key",
|
|
638
|
+
oauthProvider: "hubspot",
|
|
639
|
+
tokenLabel: "HubSpot OAuth token",
|
|
640
|
+
key: "HUBSPOT_PRIVATE_APP_TOKEN",
|
|
641
|
+
fallbackKeys: ["HUBSPOT_PRIVATE_APP_TOKEN", "HUBSPOT_ACCESS_TOKEN"],
|
|
637
642
|
workspaceProvider: "hubspot",
|
|
638
643
|
},
|
|
639
644
|
credentialKeys: ["HUBSPOT_PRIVATE_APP_TOKEN", "HUBSPOT_ACCESS_TOKEN"],
|
|
@@ -673,9 +678,12 @@ const PROVIDER_CONFIGS = {
|
|
|
673
678
|
defaultBaseUrl: "https://example.atlassian.net",
|
|
674
679
|
baseUrlCredentialKey: "JIRA_BASE_URL",
|
|
675
680
|
auth: {
|
|
676
|
-
type: "basic",
|
|
681
|
+
type: "oauth-bearer-or-basic",
|
|
682
|
+
oauthProvider: "jira",
|
|
683
|
+
tokenLabel: "Jira",
|
|
677
684
|
usernameKey: "JIRA_USER_EMAIL",
|
|
678
685
|
passwordKey: "JIRA_API_TOKEN",
|
|
686
|
+
workspaceProvider: "jira",
|
|
679
687
|
},
|
|
680
688
|
credentialKeys: ["JIRA_BASE_URL", "JIRA_USER_EMAIL", "JIRA_API_TOKEN"],
|
|
681
689
|
docsUrls: [
|
|
@@ -807,8 +815,12 @@ const PROVIDER_CONFIGS = {
|
|
|
807
815
|
label: "Sentry",
|
|
808
816
|
defaultBaseUrl: "https://sentry.io/api/0",
|
|
809
817
|
auth: {
|
|
810
|
-
type: "bearer",
|
|
811
|
-
|
|
818
|
+
type: "oauth-bearer-or-bearer-key",
|
|
819
|
+
oauthProvider: "sentry",
|
|
820
|
+
tokenLabel: "Sentry OAuth token",
|
|
821
|
+
key: "SENTRY_AUTH_TOKEN",
|
|
822
|
+
fallbackKeys: ["SENTRY_AUTH_TOKEN", "SENTRY_SERVER_TOKEN"],
|
|
823
|
+
workspaceProvider: "sentry",
|
|
812
824
|
},
|
|
813
825
|
credentialKeys: [
|
|
814
826
|
"SENTRY_AUTH_TOKEN",
|
|
@@ -939,6 +951,7 @@ export function createProviderApiRuntime(options) {
|
|
|
939
951
|
listCatalog: (provider) => listProviderApiCatalogWithCustom(provider, { providerIds }, runtimeOptions),
|
|
940
952
|
fetchDocs: (docsOptions) => fetchProviderApiDocs(docsOptions, runtimeOptions),
|
|
941
953
|
executeRequest: (args) => executeProviderApiRequest(args, runtimeOptions),
|
|
954
|
+
resolveOAuthAccessToken: (args) => resolveProviderApiOAuthAccessToken(args, runtimeOptions),
|
|
942
955
|
listGitHubRepositoryFiles: (args) => listGitHubRepositoryFiles(args, runtimeOptions),
|
|
943
956
|
searchGitHubRepositoryFiles: (args) => searchGitHubRepositoryFiles(args, runtimeOptions),
|
|
944
957
|
readGitHubRepositoryFile: (args) => readGitHubRepositoryFile(args, runtimeOptions),
|
|
@@ -1201,24 +1214,45 @@ export async function resolveProviderApiOAuthAccessToken(args, runtime) {
|
|
|
1201
1214
|
await assertProviderAllowedAsync(args.provider, runtime);
|
|
1202
1215
|
const config = getProviderApiConfig(args.provider);
|
|
1203
1216
|
const auth = config.auth;
|
|
1204
|
-
|
|
1217
|
+
const oauthAuth = auth.type === "oauth-bearer"
|
|
1218
|
+
? auth
|
|
1219
|
+
: auth.type === "oauth-bearer-or-api-key-header" ||
|
|
1220
|
+
auth.type === "oauth-bearer-or-bearer-key"
|
|
1221
|
+
? {
|
|
1222
|
+
type: "oauth-bearer",
|
|
1223
|
+
oauthProvider: auth.oauthProvider,
|
|
1224
|
+
tokenLabel: auth.tokenLabel,
|
|
1225
|
+
workspaceProvider: auth.workspaceProvider,
|
|
1226
|
+
}
|
|
1227
|
+
: auth.type === "oauth-bearer-or-basic"
|
|
1228
|
+
? {
|
|
1229
|
+
type: "oauth-bearer",
|
|
1230
|
+
oauthProvider: auth.oauthProvider,
|
|
1231
|
+
tokenLabel: auth.tokenLabel,
|
|
1232
|
+
workspaceProvider: auth.workspaceProvider,
|
|
1233
|
+
}
|
|
1234
|
+
: null;
|
|
1235
|
+
if (!oauthAuth) {
|
|
1205
1236
|
throw new Error(`Provider API ${args.provider} does not use a direct OAuth bearer token.`);
|
|
1206
1237
|
}
|
|
1207
1238
|
const ctx = requireRuntimeCredentialContext(runtime, config.credentialKeys[0] ?? config.id);
|
|
1208
|
-
const credential =
|
|
1209
|
-
? await
|
|
1210
|
-
auth,
|
|
1239
|
+
const credential = oauthAuth.workspaceProvider
|
|
1240
|
+
? await resolveOptionalConnectionBoundOAuthBearerToken({
|
|
1241
|
+
auth: oauthAuth,
|
|
1211
1242
|
runtime,
|
|
1212
1243
|
ctx,
|
|
1213
|
-
workspaceProvider:
|
|
1244
|
+
workspaceProvider: oauthAuth.workspaceProvider,
|
|
1214
1245
|
connectionId: args.connectionId,
|
|
1215
1246
|
accountId: args.accountId,
|
|
1216
1247
|
})
|
|
1217
1248
|
: await resolveOAuthBearerToken({
|
|
1218
|
-
auth,
|
|
1249
|
+
auth: oauthAuth,
|
|
1219
1250
|
ctx,
|
|
1220
1251
|
accountId: args.accountId,
|
|
1221
1252
|
});
|
|
1253
|
+
if (!credential) {
|
|
1254
|
+
throw new Error(`${oauthAuth.tokenLabel} workspace connection is not available to ${runtime.appId}.`);
|
|
1255
|
+
}
|
|
1222
1256
|
return {
|
|
1223
1257
|
accessToken: credential.value,
|
|
1224
1258
|
accountId: credential.accountId ?? null,
|
|
@@ -2184,10 +2218,13 @@ function describeAuth(auth) {
|
|
|
2184
2218
|
if (auth.type === "oauth-bearer")
|
|
2185
2219
|
return `oauth-bearer:${auth.oauthProvider}`;
|
|
2186
2220
|
if (auth.type === "oauth-bearer-or-api-key-header") {
|
|
2187
|
-
return `oauth-bearer:${auth.oauthProvider}-or-api-key-header:${auth.header}`;
|
|
2221
|
+
return `oauth-bearer:${auth.oauthProvider}-or-api-key-header:${auth.header}:${(auth.fallbackKeys ?? [auth.key]).join(",")}`;
|
|
2188
2222
|
}
|
|
2189
2223
|
if (auth.type === "oauth-bearer-or-bearer-key") {
|
|
2190
|
-
return `oauth-bearer:${auth.oauthProvider}-or-bearer-key:${auth.key}`;
|
|
2224
|
+
return `oauth-bearer:${auth.oauthProvider}-or-bearer-key:${(auth.fallbackKeys ?? [auth.key]).join(",")}`;
|
|
2225
|
+
}
|
|
2226
|
+
if (auth.type === "oauth-bearer-or-basic") {
|
|
2227
|
+
return `oauth-bearer:${auth.oauthProvider}-or-basic:${auth.usernameKey}:${auth.passwordKey}`;
|
|
2191
2228
|
}
|
|
2192
2229
|
return "prometheus-basic-or-bearer";
|
|
2193
2230
|
}
|
|
@@ -2199,17 +2236,64 @@ function requireRuntimeCredentialContext(runtime, credentialKey) {
|
|
|
2199
2236
|
return ctx;
|
|
2200
2237
|
}
|
|
2201
2238
|
async function resolveBaseUrl(config, runtime, ctx, args) {
|
|
2239
|
+
const oauthBaseUrl = await resolveWorkspaceOAuthBaseUrl(config, runtime, args);
|
|
2240
|
+
if (oauthBaseUrl)
|
|
2241
|
+
return oauthBaseUrl;
|
|
2202
2242
|
if (!config.baseUrlCredentialKey)
|
|
2203
2243
|
return config.defaultBaseUrl;
|
|
2244
|
+
const auth = config.auth;
|
|
2245
|
+
const workspaceProvider = auth.type === "oauth-bearer" ||
|
|
2246
|
+
auth.type === "oauth-bearer-or-api-key-header" ||
|
|
2247
|
+
auth.type === "oauth-bearer-or-bearer-key" ||
|
|
2248
|
+
auth.type === "oauth-bearer-or-basic"
|
|
2249
|
+
? auth.workspaceProvider
|
|
2250
|
+
: undefined;
|
|
2204
2251
|
const configured = await resolveCredentialValue({
|
|
2205
2252
|
config,
|
|
2206
2253
|
runtime,
|
|
2207
2254
|
ctx,
|
|
2208
2255
|
key: config.baseUrlCredentialKey,
|
|
2209
2256
|
args,
|
|
2257
|
+
workspaceProvider,
|
|
2210
2258
|
});
|
|
2211
2259
|
return (configured || config.defaultBaseUrl).replace(/\/+$/, "");
|
|
2212
2260
|
}
|
|
2261
|
+
async function resolveWorkspaceOAuthBaseUrl(config, runtime, args) {
|
|
2262
|
+
const auth = config.auth;
|
|
2263
|
+
const workspaceProvider = auth.type === "oauth-bearer" ||
|
|
2264
|
+
auth.type === "oauth-bearer-or-api-key-header" ||
|
|
2265
|
+
auth.type === "oauth-bearer-or-bearer-key" ||
|
|
2266
|
+
auth.type === "oauth-bearer-or-basic"
|
|
2267
|
+
? auth.workspaceProvider
|
|
2268
|
+
: undefined;
|
|
2269
|
+
if (workspaceProvider !== "jira")
|
|
2270
|
+
return null;
|
|
2271
|
+
let resolved;
|
|
2272
|
+
try {
|
|
2273
|
+
resolved = await resolveWorkspaceConnectionForApp({
|
|
2274
|
+
appId: runtime.appId,
|
|
2275
|
+
provider: workspaceProvider,
|
|
2276
|
+
connectionId: args.connectionId ?? undefined,
|
|
2277
|
+
requireConnected: true,
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
catch (error) {
|
|
2281
|
+
if (error instanceof Error &&
|
|
2282
|
+
error.message === "Workspace connections require an authenticated user.") {
|
|
2283
|
+
return null;
|
|
2284
|
+
}
|
|
2285
|
+
throw error;
|
|
2286
|
+
}
|
|
2287
|
+
if (!resolved.available || !resolved.connection)
|
|
2288
|
+
return null;
|
|
2289
|
+
const connectionConfig = resolved.connection.config;
|
|
2290
|
+
if (connectionConfig.credentialMode !== "oauth")
|
|
2291
|
+
return null;
|
|
2292
|
+
const baseUrl = connectionConfig.atlassianApiBaseUrl;
|
|
2293
|
+
if (typeof baseUrl !== "string" || !baseUrl.trim())
|
|
2294
|
+
return null;
|
|
2295
|
+
return baseUrl.replace(/\/+$/, "");
|
|
2296
|
+
}
|
|
2213
2297
|
async function resolvePlaceholders(config, runtime, ctx, args) {
|
|
2214
2298
|
const placeholders = {};
|
|
2215
2299
|
for (const placeholder of config.placeholders ?? []) {
|
|
@@ -2440,29 +2524,63 @@ async function resolveAuth(config, runtime, ctx, args) {
|
|
|
2440
2524
|
};
|
|
2441
2525
|
}
|
|
2442
2526
|
if (auth.type === "oauth-bearer-or-api-key-header") {
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
tokenLabel: auth.tokenLabel,
|
|
2449
|
-
},
|
|
2527
|
+
const explicitConnectionFallback = Boolean(args.connectionId?.trim()) && Boolean(runtime.resolveCredential);
|
|
2528
|
+
const resolvedFallback = explicitConnectionFallback
|
|
2529
|
+
? await resolveHybridFallbackCredential({
|
|
2530
|
+
auth,
|
|
2531
|
+
config,
|
|
2450
2532
|
runtime,
|
|
2451
2533
|
ctx,
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2534
|
+
args,
|
|
2535
|
+
})
|
|
2536
|
+
: null;
|
|
2537
|
+
if (resolvedFallback) {
|
|
2456
2538
|
return {
|
|
2457
|
-
headers: {
|
|
2458
|
-
credentialSources: [omitCredentialValue(
|
|
2459
|
-
secretValues: [
|
|
2539
|
+
headers: { [auth.header]: resolvedFallback.value },
|
|
2540
|
+
credentialSources: [omitCredentialValue(resolvedFallback)],
|
|
2541
|
+
secretValues: [resolvedFallback.value],
|
|
2460
2542
|
};
|
|
2461
2543
|
}
|
|
2462
|
-
const
|
|
2544
|
+
const workspaceCredential = await resolveOptionalConnectionBoundOAuthBearerToken({
|
|
2545
|
+
auth: {
|
|
2546
|
+
type: "oauth-bearer",
|
|
2547
|
+
oauthProvider: auth.oauthProvider,
|
|
2548
|
+
tokenLabel: auth.tokenLabel,
|
|
2549
|
+
},
|
|
2550
|
+
runtime,
|
|
2551
|
+
ctx,
|
|
2552
|
+
connectionId: args.connectionId,
|
|
2553
|
+
accountId: args.accountId,
|
|
2554
|
+
workspaceProvider: auth.workspaceProvider,
|
|
2555
|
+
allowUnbound: true,
|
|
2556
|
+
});
|
|
2557
|
+
if (workspaceCredential) {
|
|
2558
|
+
return {
|
|
2559
|
+
headers: { Authorization: `Bearer ${workspaceCredential.value}` },
|
|
2560
|
+
credentialSources: [omitCredentialValue(workspaceCredential)],
|
|
2561
|
+
secretValues: [workspaceCredential.value],
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
if (!explicitConnectionFallback) {
|
|
2565
|
+
const resolvedFallback = await resolveHybridFallbackCredential({
|
|
2566
|
+
auth,
|
|
2567
|
+
config,
|
|
2568
|
+
runtime,
|
|
2569
|
+
ctx,
|
|
2570
|
+
args,
|
|
2571
|
+
});
|
|
2572
|
+
if (resolvedFallback) {
|
|
2573
|
+
return {
|
|
2574
|
+
headers: { [auth.header]: resolvedFallback.value },
|
|
2575
|
+
credentialSources: [omitCredentialValue(resolvedFallback)],
|
|
2576
|
+
secretValues: [resolvedFallback.value],
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
const credential = await resolveAnyCredential({
|
|
2463
2581
|
provider: config.id,
|
|
2464
2582
|
workspaceProvider: auth.workspaceProvider,
|
|
2465
|
-
|
|
2583
|
+
keys: auth.fallbackKeys ?? [auth.key],
|
|
2466
2584
|
ctx,
|
|
2467
2585
|
runtime,
|
|
2468
2586
|
});
|
|
@@ -2473,29 +2591,63 @@ async function resolveAuth(config, runtime, ctx, args) {
|
|
|
2473
2591
|
};
|
|
2474
2592
|
}
|
|
2475
2593
|
if (auth.type === "oauth-bearer-or-bearer-key") {
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
tokenLabel: auth.tokenLabel,
|
|
2482
|
-
},
|
|
2594
|
+
const explicitConnectionFallback = Boolean(args.connectionId?.trim()) && Boolean(runtime.resolveCredential);
|
|
2595
|
+
const resolvedFallback = explicitConnectionFallback
|
|
2596
|
+
? await resolveHybridFallbackCredential({
|
|
2597
|
+
auth,
|
|
2598
|
+
config,
|
|
2483
2599
|
runtime,
|
|
2484
2600
|
ctx,
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2601
|
+
args,
|
|
2602
|
+
})
|
|
2603
|
+
: null;
|
|
2604
|
+
if (resolvedFallback) {
|
|
2489
2605
|
return {
|
|
2490
|
-
headers: { Authorization: `Bearer ${
|
|
2491
|
-
credentialSources: [omitCredentialValue(
|
|
2492
|
-
secretValues: [
|
|
2606
|
+
headers: { Authorization: `Bearer ${resolvedFallback.value}` },
|
|
2607
|
+
credentialSources: [omitCredentialValue(resolvedFallback)],
|
|
2608
|
+
secretValues: [resolvedFallback.value],
|
|
2493
2609
|
};
|
|
2494
2610
|
}
|
|
2495
|
-
const
|
|
2611
|
+
const workspaceCredential = await resolveOptionalConnectionBoundOAuthBearerToken({
|
|
2612
|
+
auth: {
|
|
2613
|
+
type: "oauth-bearer",
|
|
2614
|
+
oauthProvider: auth.oauthProvider,
|
|
2615
|
+
tokenLabel: auth.tokenLabel,
|
|
2616
|
+
},
|
|
2617
|
+
runtime,
|
|
2618
|
+
ctx,
|
|
2619
|
+
connectionId: args.connectionId,
|
|
2620
|
+
accountId: args.accountId,
|
|
2621
|
+
workspaceProvider: auth.workspaceProvider,
|
|
2622
|
+
allowUnbound: true,
|
|
2623
|
+
});
|
|
2624
|
+
if (workspaceCredential) {
|
|
2625
|
+
return {
|
|
2626
|
+
headers: { Authorization: `Bearer ${workspaceCredential.value}` },
|
|
2627
|
+
credentialSources: [omitCredentialValue(workspaceCredential)],
|
|
2628
|
+
secretValues: [workspaceCredential.value],
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
if (!explicitConnectionFallback) {
|
|
2632
|
+
const resolvedFallback = await resolveHybridFallbackCredential({
|
|
2633
|
+
auth,
|
|
2634
|
+
config,
|
|
2635
|
+
runtime,
|
|
2636
|
+
ctx,
|
|
2637
|
+
args,
|
|
2638
|
+
});
|
|
2639
|
+
if (resolvedFallback) {
|
|
2640
|
+
return {
|
|
2641
|
+
headers: { Authorization: `Bearer ${resolvedFallback.value}` },
|
|
2642
|
+
credentialSources: [omitCredentialValue(resolvedFallback)],
|
|
2643
|
+
secretValues: [resolvedFallback.value],
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
const credential = await resolveAnyCredential({
|
|
2496
2648
|
provider: config.id,
|
|
2497
2649
|
workspaceProvider: auth.workspaceProvider,
|
|
2498
|
-
|
|
2650
|
+
keys: auth.fallbackKeys ?? [auth.key],
|
|
2499
2651
|
ctx,
|
|
2500
2652
|
runtime,
|
|
2501
2653
|
});
|
|
@@ -2505,6 +2657,53 @@ async function resolveAuth(config, runtime, ctx, args) {
|
|
|
2505
2657
|
secretValues: [credential.value],
|
|
2506
2658
|
};
|
|
2507
2659
|
}
|
|
2660
|
+
if (auth.type === "oauth-bearer-or-basic") {
|
|
2661
|
+
const workspaceCredential = await resolveOptionalConnectionBoundOAuthBearerToken({
|
|
2662
|
+
auth: {
|
|
2663
|
+
type: "oauth-bearer",
|
|
2664
|
+
oauthProvider: auth.oauthProvider,
|
|
2665
|
+
tokenLabel: auth.tokenLabel,
|
|
2666
|
+
},
|
|
2667
|
+
runtime,
|
|
2668
|
+
ctx,
|
|
2669
|
+
connectionId: args.connectionId,
|
|
2670
|
+
accountId: args.accountId,
|
|
2671
|
+
workspaceProvider: auth.workspaceProvider,
|
|
2672
|
+
allowUnbound: true,
|
|
2673
|
+
});
|
|
2674
|
+
if (workspaceCredential) {
|
|
2675
|
+
return {
|
|
2676
|
+
headers: { Authorization: `Bearer ${workspaceCredential.value}` },
|
|
2677
|
+
credentialSources: [omitCredentialValue(workspaceCredential)],
|
|
2678
|
+
secretValues: [workspaceCredential.value],
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
const username = await resolveRequiredCredential({
|
|
2682
|
+
provider: config.id,
|
|
2683
|
+
workspaceProvider: auth.workspaceProvider,
|
|
2684
|
+
key: auth.usernameKey,
|
|
2685
|
+
ctx,
|
|
2686
|
+
runtime,
|
|
2687
|
+
connectionId: args.connectionId,
|
|
2688
|
+
});
|
|
2689
|
+
const password = await resolveRequiredCredential({
|
|
2690
|
+
provider: config.id,
|
|
2691
|
+
workspaceProvider: auth.workspaceProvider,
|
|
2692
|
+
key: auth.passwordKey,
|
|
2693
|
+
ctx,
|
|
2694
|
+
runtime,
|
|
2695
|
+
connectionId: args.connectionId,
|
|
2696
|
+
});
|
|
2697
|
+
const encoded = Buffer.from(`${username.value}:${password.value}`).toString("base64");
|
|
2698
|
+
return {
|
|
2699
|
+
headers: { Authorization: `Basic ${encoded}` },
|
|
2700
|
+
credentialSources: [
|
|
2701
|
+
omitCredentialValue(username),
|
|
2702
|
+
omitCredentialValue(password),
|
|
2703
|
+
],
|
|
2704
|
+
secretValues: [username.value, password.value, encoded],
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2508
2707
|
const bearer = await resolveCredentialValue({
|
|
2509
2708
|
config,
|
|
2510
2709
|
runtime,
|
|
@@ -2563,6 +2762,20 @@ async function resolveAuth(config, runtime, ctx, args) {
|
|
|
2563
2762
|
function emptyAuth() {
|
|
2564
2763
|
return { headers: {}, credentialSources: [], secretValues: [] };
|
|
2565
2764
|
}
|
|
2765
|
+
async function resolveHybridFallbackCredential(options) {
|
|
2766
|
+
// App-specific resolvers keep existing provider credentials working when a
|
|
2767
|
+
// caller explicitly selects a connection or account.
|
|
2768
|
+
if (!options.runtime.resolveCredential)
|
|
2769
|
+
return null;
|
|
2770
|
+
return resolveOptionalCredential({
|
|
2771
|
+
provider: options.config.id,
|
|
2772
|
+
workspaceProvider: options.auth.workspaceProvider,
|
|
2773
|
+
key: options.auth.key,
|
|
2774
|
+
ctx: options.ctx,
|
|
2775
|
+
runtime: options.runtime,
|
|
2776
|
+
connectionId: options.args.connectionId,
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2566
2779
|
async function resolveAnyCredential(options) {
|
|
2567
2780
|
for (const key of options.keys) {
|
|
2568
2781
|
const credential = await resolveOptionalCredential({ ...options, key });
|
|
@@ -2653,22 +2866,23 @@ async function getGoogleServiceAccountToken(scopes, runtime, ctx) {
|
|
|
2653
2866
|
return data.access_token;
|
|
2654
2867
|
}
|
|
2655
2868
|
async function resolveOAuthBearerToken(options) {
|
|
2656
|
-
const
|
|
2869
|
+
const ownerEmail = options.ownerEmail?.trim() || options.ctx.userEmail;
|
|
2870
|
+
const accounts = await listOAuthAccountsByOwner(options.auth.oauthProvider, ownerEmail);
|
|
2657
2871
|
if (accounts.length === 0) {
|
|
2658
|
-
throw new Error(`${options.auth.tokenLabel} is not connected for ${
|
|
2872
|
+
throw new Error(`${options.auth.tokenLabel} is not connected for ${ownerEmail}.`);
|
|
2659
2873
|
}
|
|
2660
2874
|
const accountId = options.accountId?.trim();
|
|
2661
2875
|
const account = accountId
|
|
2662
2876
|
? accounts.find((entry) => entry.accountId === accountId)
|
|
2663
2877
|
: accounts[0];
|
|
2664
2878
|
if (!account) {
|
|
2665
|
-
throw new Error(`${options.auth.tokenLabel} account ${accountId} is not available to ${
|
|
2879
|
+
throw new Error(`${options.auth.tokenLabel} account ${accountId} is not available to ${ownerEmail}.`);
|
|
2666
2880
|
}
|
|
2667
2881
|
const tokens = account.tokens;
|
|
2668
2882
|
const token = await getValidOAuthAccessToken({
|
|
2669
2883
|
oauthProvider: options.auth.oauthProvider,
|
|
2670
2884
|
accountId: account.accountId,
|
|
2671
|
-
ownerEmail
|
|
2885
|
+
ownerEmail,
|
|
2672
2886
|
tokens,
|
|
2673
2887
|
});
|
|
2674
2888
|
return {
|
|
@@ -2681,22 +2895,39 @@ async function resolveOAuthBearerToken(options) {
|
|
|
2681
2895
|
};
|
|
2682
2896
|
}
|
|
2683
2897
|
async function resolveConnectionBoundOAuthBearerToken(options) {
|
|
2898
|
+
const credential = await resolveOptionalConnectionBoundOAuthBearerToken(options);
|
|
2899
|
+
if (credential)
|
|
2900
|
+
return credential;
|
|
2901
|
+
throw new Error(`${options.auth.tokenLabel} requires an available workspace connection.`);
|
|
2902
|
+
}
|
|
2903
|
+
async function resolveOptionalConnectionBoundOAuthBearerToken(options) {
|
|
2684
2904
|
const requestedConnectionId = options.connectionId?.trim();
|
|
2685
|
-
|
|
2686
|
-
|
|
2905
|
+
let resolved;
|
|
2906
|
+
try {
|
|
2907
|
+
resolved = await resolveWorkspaceConnectionForApp({
|
|
2908
|
+
appId: options.runtime.appId,
|
|
2909
|
+
provider: options.workspaceProvider,
|
|
2910
|
+
connectionId: requestedConnectionId,
|
|
2911
|
+
requireConnected: true,
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
catch (error) {
|
|
2915
|
+
if (error instanceof Error &&
|
|
2916
|
+
error.message === "Workspace connections require an authenticated user.") {
|
|
2917
|
+
return null;
|
|
2918
|
+
}
|
|
2919
|
+
throw error;
|
|
2687
2920
|
}
|
|
2688
|
-
const resolved = await resolveWorkspaceConnectionForApp({
|
|
2689
|
-
appId: options.runtime.appId,
|
|
2690
|
-
provider: options.workspaceProvider,
|
|
2691
|
-
connectionId: requestedConnectionId,
|
|
2692
|
-
requireConnected: true,
|
|
2693
|
-
});
|
|
2694
2921
|
if (!resolved.available || !resolved.connection) {
|
|
2695
|
-
|
|
2922
|
+
if (requestedConnectionId)
|
|
2923
|
+
throw new Error(resolved.reason);
|
|
2924
|
+
return null;
|
|
2696
2925
|
}
|
|
2697
2926
|
const connectionAccountId = resolved.connection.accountId?.trim();
|
|
2698
2927
|
if (!connectionAccountId) {
|
|
2699
|
-
|
|
2928
|
+
if (options.allowUnbound)
|
|
2929
|
+
return null;
|
|
2930
|
+
throw new Error(`${options.auth.tokenLabel} workspace connection ${resolved.connection.id} is missing its OAuth account binding.`);
|
|
2700
2931
|
}
|
|
2701
2932
|
const requestedAccountId = options.accountId?.trim();
|
|
2702
2933
|
if (requestedAccountId && requestedAccountId !== connectionAccountId) {
|
|
@@ -2706,6 +2937,7 @@ async function resolveConnectionBoundOAuthBearerToken(options) {
|
|
|
2706
2937
|
auth: options.auth,
|
|
2707
2938
|
ctx: options.ctx,
|
|
2708
2939
|
accountId: connectionAccountId,
|
|
2940
|
+
ownerEmail: resolved.connection.ownerEmail,
|
|
2709
2941
|
});
|
|
2710
2942
|
return {
|
|
2711
2943
|
...credential,
|
|
@@ -2737,6 +2969,18 @@ async function getValidOAuthAccessToken(options) {
|
|
|
2737
2969
|
if (options.oauthProvider === "notion") {
|
|
2738
2970
|
return refreshNotionOAuthToken(options, refreshToken);
|
|
2739
2971
|
}
|
|
2972
|
+
if (options.oauthProvider === "github") {
|
|
2973
|
+
return refreshGitHubOAuthToken(options, refreshToken);
|
|
2974
|
+
}
|
|
2975
|
+
if (options.oauthProvider === "hubspot") {
|
|
2976
|
+
return refreshHubSpotOAuthToken(options, refreshToken);
|
|
2977
|
+
}
|
|
2978
|
+
if (options.oauthProvider === "jira") {
|
|
2979
|
+
return refreshJiraOAuthToken(options, refreshToken);
|
|
2980
|
+
}
|
|
2981
|
+
if (options.oauthProvider === "sentry") {
|
|
2982
|
+
return refreshSentryOAuthToken(options, refreshToken);
|
|
2983
|
+
}
|
|
2740
2984
|
throw new Error(`${options.oauthProvider} OAuth token is expired and automatic refresh is not configured for provider-api.`);
|
|
2741
2985
|
}
|
|
2742
2986
|
async function refreshFigmaOAuthToken(options, refreshToken) {
|
|
@@ -2804,6 +3048,179 @@ async function refreshNotionOAuthToken(options, refreshToken) {
|
|
|
2804
3048
|
await saveOAuthTokens(options.oauthProvider, options.accountId, updated, options.ownerEmail);
|
|
2805
3049
|
return data.access_token;
|
|
2806
3050
|
}
|
|
3051
|
+
async function refreshGitHubOAuthToken(options, refreshToken) {
|
|
3052
|
+
const credentials = await resolveOAuthClientCredentialCandidates("GITHUB");
|
|
3053
|
+
if (!credentials.length) {
|
|
3054
|
+
throw new Error("GITHUB_CLIENT_ID/SECRET not set for GitHub OAuth refresh.");
|
|
3055
|
+
}
|
|
3056
|
+
let response = null;
|
|
3057
|
+
let data = {};
|
|
3058
|
+
for (const credential of credentials) {
|
|
3059
|
+
const result = await fetchBoundedOAuthRefreshJson("https://github.com/login/oauth/access_token", {
|
|
3060
|
+
method: "POST",
|
|
3061
|
+
headers: {
|
|
3062
|
+
Accept: "application/json",
|
|
3063
|
+
"Content-Type": "application/json",
|
|
3064
|
+
},
|
|
3065
|
+
body: JSON.stringify({
|
|
3066
|
+
client_id: credential.clientId,
|
|
3067
|
+
client_secret: credential.clientSecret,
|
|
3068
|
+
grant_type: "refresh_token",
|
|
3069
|
+
refresh_token: refreshToken,
|
|
3070
|
+
}),
|
|
3071
|
+
}, "GitHub");
|
|
3072
|
+
response = result.response;
|
|
3073
|
+
data = result.data;
|
|
3074
|
+
if (response.ok && data.access_token)
|
|
3075
|
+
break;
|
|
3076
|
+
}
|
|
3077
|
+
if (!response?.ok || !data.access_token) {
|
|
3078
|
+
throw new Error(`GitHub OAuth refresh failed (${response?.status ?? 0}).`);
|
|
3079
|
+
}
|
|
3080
|
+
const updated = {
|
|
3081
|
+
...options.tokens,
|
|
3082
|
+
access_token: data.access_token,
|
|
3083
|
+
refresh_token: data.refresh_token ?? refreshToken,
|
|
3084
|
+
token_type: data.token_type ?? options.tokens.token_type,
|
|
3085
|
+
...(Number.isFinite(data.expires_in) && (data.expires_in ?? 0) > 0
|
|
3086
|
+
? { expiry_date: Date.now() + data.expires_in * 1_000 }
|
|
3087
|
+
: {}),
|
|
3088
|
+
};
|
|
3089
|
+
await saveOAuthTokens(options.oauthProvider, options.accountId, updated, options.ownerEmail);
|
|
3090
|
+
return data.access_token;
|
|
3091
|
+
}
|
|
3092
|
+
async function saveOAuthTokensAndLinkedJiraAccounts(options) {
|
|
3093
|
+
const serializedTokens = options.updated;
|
|
3094
|
+
await saveOAuthTokens(options.options.oauthProvider, options.options.accountId, serializedTokens, options.options.ownerEmail);
|
|
3095
|
+
const accounts = await listOAuthAccountsByOwner(options.options.oauthProvider, options.options.ownerEmail);
|
|
3096
|
+
for (const account of accounts) {
|
|
3097
|
+
if (account.accountId === options.options.accountId)
|
|
3098
|
+
continue;
|
|
3099
|
+
const accountRefreshToken = account.tokens.refresh_token ??
|
|
3100
|
+
account.tokens.refreshToken;
|
|
3101
|
+
if (accountRefreshToken !== options.previousRefreshToken)
|
|
3102
|
+
continue;
|
|
3103
|
+
await saveOAuthTokens(options.options.oauthProvider, account.accountId, serializedTokens, options.options.ownerEmail);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
async function refreshHubSpotOAuthToken(options, refreshToken) {
|
|
3107
|
+
const credentials = await resolveOAuthClientCredentialCandidates("HUBSPOT");
|
|
3108
|
+
if (!credentials.length) {
|
|
3109
|
+
throw new Error("HUBSPOT_CLIENT_ID/SECRET not set for HubSpot OAuth refresh.");
|
|
3110
|
+
}
|
|
3111
|
+
let response = null;
|
|
3112
|
+
let data = {};
|
|
3113
|
+
for (const credential of credentials) {
|
|
3114
|
+
const result = await fetchBoundedOAuthRefreshJson("https://api.hubapi.com/oauth/v3/token", {
|
|
3115
|
+
method: "POST",
|
|
3116
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3117
|
+
body: new URLSearchParams({
|
|
3118
|
+
grant_type: "refresh_token",
|
|
3119
|
+
client_id: credential.clientId,
|
|
3120
|
+
client_secret: credential.clientSecret,
|
|
3121
|
+
refresh_token: refreshToken,
|
|
3122
|
+
}),
|
|
3123
|
+
}, "HubSpot");
|
|
3124
|
+
response = result.response;
|
|
3125
|
+
data = result.data;
|
|
3126
|
+
if (response.ok && data.access_token)
|
|
3127
|
+
break;
|
|
3128
|
+
}
|
|
3129
|
+
if (!response?.ok || !data.access_token) {
|
|
3130
|
+
throw new Error(`HubSpot OAuth refresh failed (${response?.status ?? 0}).`);
|
|
3131
|
+
}
|
|
3132
|
+
const updated = {
|
|
3133
|
+
...options.tokens,
|
|
3134
|
+
access_token: data.access_token,
|
|
3135
|
+
refresh_token: data.refresh_token ?? refreshToken,
|
|
3136
|
+
token_type: data.token_type ?? options.tokens.token_type,
|
|
3137
|
+
...(Number.isFinite(data.expires_in) && (data.expires_in ?? 0) > 0
|
|
3138
|
+
? { expiry_date: Date.now() + data.expires_in * 1_000 }
|
|
3139
|
+
: {}),
|
|
3140
|
+
};
|
|
3141
|
+
await saveOAuthTokens(options.oauthProvider, options.accountId, updated, options.ownerEmail);
|
|
3142
|
+
return data.access_token;
|
|
3143
|
+
}
|
|
3144
|
+
async function refreshJiraOAuthToken(options, refreshToken) {
|
|
3145
|
+
const credentials = await resolveOAuthClientCredentialCandidates("JIRA");
|
|
3146
|
+
if (!credentials.length) {
|
|
3147
|
+
throw new Error("JIRA_CLIENT_ID/SECRET not set for Jira OAuth refresh.");
|
|
3148
|
+
}
|
|
3149
|
+
let response = null;
|
|
3150
|
+
let data = {};
|
|
3151
|
+
for (const credential of credentials) {
|
|
3152
|
+
const result = await fetchBoundedOAuthRefreshJson("https://auth.atlassian.com/oauth/token", {
|
|
3153
|
+
method: "POST",
|
|
3154
|
+
headers: { "Content-Type": "application/json" },
|
|
3155
|
+
body: JSON.stringify({
|
|
3156
|
+
grant_type: "refresh_token",
|
|
3157
|
+
client_id: credential.clientId,
|
|
3158
|
+
client_secret: credential.clientSecret,
|
|
3159
|
+
refresh_token: refreshToken,
|
|
3160
|
+
}),
|
|
3161
|
+
}, "Jira");
|
|
3162
|
+
response = result.response;
|
|
3163
|
+
data = result.data;
|
|
3164
|
+
if (response.ok && data.access_token)
|
|
3165
|
+
break;
|
|
3166
|
+
}
|
|
3167
|
+
if (!response?.ok || !data.access_token) {
|
|
3168
|
+
throw new Error(`Jira OAuth refresh failed (${response?.status ?? 0}).`);
|
|
3169
|
+
}
|
|
3170
|
+
const updated = {
|
|
3171
|
+
...options.tokens,
|
|
3172
|
+
access_token: data.access_token,
|
|
3173
|
+
refresh_token: data.refresh_token ?? refreshToken,
|
|
3174
|
+
token_type: data.token_type ?? options.tokens.token_type,
|
|
3175
|
+
...(Number.isFinite(data.expires_in) && (data.expires_in ?? 0) > 0
|
|
3176
|
+
? { expiry_date: Date.now() + data.expires_in * 1_000 }
|
|
3177
|
+
: {}),
|
|
3178
|
+
};
|
|
3179
|
+
await saveOAuthTokensAndLinkedJiraAccounts({
|
|
3180
|
+
options,
|
|
3181
|
+
previousRefreshToken: refreshToken,
|
|
3182
|
+
updated,
|
|
3183
|
+
});
|
|
3184
|
+
return data.access_token;
|
|
3185
|
+
}
|
|
3186
|
+
async function refreshSentryOAuthToken(options, refreshToken) {
|
|
3187
|
+
const credentials = await resolveOAuthClientCredentialCandidates("SENTRY");
|
|
3188
|
+
if (!credentials.length) {
|
|
3189
|
+
throw new Error("SENTRY_CLIENT_ID/SECRET not set for Sentry OAuth refresh.");
|
|
3190
|
+
}
|
|
3191
|
+
let response = null;
|
|
3192
|
+
let data = {};
|
|
3193
|
+
for (const credential of credentials) {
|
|
3194
|
+
const result = await fetchBoundedOAuthRefreshJson("https://sentry.io/oauth/token/", {
|
|
3195
|
+
method: "POST",
|
|
3196
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3197
|
+
body: new URLSearchParams({
|
|
3198
|
+
grant_type: "refresh_token",
|
|
3199
|
+
client_id: credential.clientId,
|
|
3200
|
+
client_secret: credential.clientSecret,
|
|
3201
|
+
refresh_token: refreshToken,
|
|
3202
|
+
}),
|
|
3203
|
+
}, "Sentry");
|
|
3204
|
+
response = result.response;
|
|
3205
|
+
data = result.data;
|
|
3206
|
+
if (response.ok && data.access_token)
|
|
3207
|
+
break;
|
|
3208
|
+
}
|
|
3209
|
+
if (!response?.ok || !data.access_token) {
|
|
3210
|
+
throw new Error(`Sentry OAuth refresh failed (${response?.status ?? 0}).`);
|
|
3211
|
+
}
|
|
3212
|
+
const updated = {
|
|
3213
|
+
...options.tokens,
|
|
3214
|
+
access_token: data.access_token,
|
|
3215
|
+
refresh_token: data.refresh_token ?? refreshToken,
|
|
3216
|
+
token_type: data.token_type ?? options.tokens.token_type,
|
|
3217
|
+
...(Number.isFinite(data.expires_in) && (data.expires_in ?? 0) > 0
|
|
3218
|
+
? { expiry_date: Date.now() + data.expires_in * 1_000 }
|
|
3219
|
+
: {}),
|
|
3220
|
+
};
|
|
3221
|
+
await saveOAuthTokens(options.oauthProvider, options.accountId, updated, options.ownerEmail);
|
|
3222
|
+
return data.access_token;
|
|
3223
|
+
}
|
|
2807
3224
|
const OAUTH_REFRESH_TIMEOUT_MS = 10_000;
|
|
2808
3225
|
const OAUTH_REFRESH_MAX_BYTES = 256 * 1024;
|
|
2809
3226
|
async function fetchBoundedOAuthRefreshJson(url, init, providerLabel) {
|
|
@@ -2922,6 +3339,27 @@ function dedupeGoogleOAuthCredentials(candidates) {
|
|
|
2922
3339
|
return true;
|
|
2923
3340
|
});
|
|
2924
3341
|
}
|
|
3342
|
+
async function resolveOAuthClientCredentialCandidates(provider) {
|
|
3343
|
+
const [clientId, clientSecret, integrationClientId, integrationClientSecret] = await Promise.all([
|
|
3344
|
+
resolveSecret(`${provider}_CLIENT_ID`),
|
|
3345
|
+
resolveSecret(`${provider}_CLIENT_SECRET`),
|
|
3346
|
+
resolveSecret(`${provider}_INTEGRATION_CLIENT_ID`),
|
|
3347
|
+
resolveSecret(`${provider}_INTEGRATION_CLIENT_SECRET`),
|
|
3348
|
+
]);
|
|
3349
|
+
const candidates = [
|
|
3350
|
+
integrationClientId && integrationClientSecret
|
|
3351
|
+
? { clientId: integrationClientId, clientSecret: integrationClientSecret }
|
|
3352
|
+
: null,
|
|
3353
|
+
clientId && clientSecret ? { clientId, clientSecret } : null,
|
|
3354
|
+
].filter((value) => Boolean(value));
|
|
3355
|
+
const seen = new Set();
|
|
3356
|
+
return candidates.filter((candidate) => {
|
|
3357
|
+
if (seen.has(candidate.clientId))
|
|
3358
|
+
return false;
|
|
3359
|
+
seen.add(candidate.clientId);
|
|
3360
|
+
return true;
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
2925
3363
|
function normalizeMethod(method) {
|
|
2926
3364
|
const normalized = String(method || "GET").toUpperCase();
|
|
2927
3365
|
if (normalized === "GET" ||
|