@tangle-network/agent-integrations 0.53.47 → 0.53.48
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/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +9 -9
- package/dist/{chunk-U36WHHML.js → chunk-CBWD2IOL.js} +251 -10
- package/dist/chunk-CBWD2IOL.js.map +1 -0
- package/dist/{chunk-TP4MD4PD.js → chunk-EZR72HCQ.js} +2 -2
- package/dist/{chunk-C7SE5X44.js → chunk-GY4PFAWA.js} +380 -63
- package/dist/chunk-GY4PFAWA.js.map +1 -0
- package/dist/{chunk-AGXTCR34.js → chunk-HNEEIMQC.js} +2427 -205
- package/dist/{chunk-AGXTCR34.js.map → chunk-HNEEIMQC.js.map} +1 -1
- package/dist/{chunk-JR2RWYQO.js → chunk-I5EF5KIT.js} +2 -2
- package/dist/{chunk-DHF5EXLO.js → chunk-PXOM5AVK.js} +7 -2
- package/dist/{chunk-DHF5EXLO.js.map → chunk-PXOM5AVK.js.map} +1 -1
- package/dist/{chunk-EKSAOR2I.js → chunk-Q7TDHVRZ.js} +2 -2
- package/dist/{chunk-WE7SQ7PB.js → chunk-RRSNINEV.js} +15 -3
- package/dist/chunk-RRSNINEV.js.map +1 -0
- package/dist/{chunk-AG4ORH2A.js → chunk-TKEVCGAI.js} +6 -6
- package/dist/chunk-TKEVCGAI.js.map +1 -0
- package/dist/connect/index.d.ts +2 -2
- package/dist/connect/index.js +2 -2
- package/dist/connectors/adapters/index.d.ts +45 -3
- package/dist/connectors/adapters/index.js +16 -2
- package/dist/connectors/index.d.ts +37 -12
- package/dist/connectors/index.js +17 -3
- package/dist/{consumer-UnsjK9iM.d.ts → consumer-BcFIDgMD.d.ts} +1 -1
- package/dist/consumer.d.ts +3 -3
- package/dist/consumer.js +2 -2
- package/dist/{core-types-Cx53j2Xp.d.ts → core-types-CE1oMiuI.d.ts} +7 -1
- package/dist/coverage-catalog.d.ts +2 -2
- package/dist/coverage-catalog.js +1 -1
- package/dist/{importers-YLXwPCz_.d.ts → importers-CHOC-2Y8.d.ts} +1 -1
- package/dist/index.d.ts +13 -12
- package/dist/index.js +23 -9
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +9 -9
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.js +2 -2
- package/dist/registry.d.ts +2 -2
- package/dist/registry.js +9 -9
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +9 -9
- package/dist/specs.d.ts +11 -3
- package/dist/specs.js +5 -5
- package/dist/{tangle-id--7mgjNkq.d.ts → tangle-id-BL_JQwKA.d.ts} +1 -1
- package/dist/{types-qFsboS2G.d.ts → types-EXGqQYoU.d.ts} +25 -10
- package/docs/integration-execution-audit.md +17 -11
- package/docs/integration-execution-matrix.json +837 -304
- package/package.json +1 -1
- package/dist/chunk-AG4ORH2A.js.map +0 -1
- package/dist/chunk-C7SE5X44.js.map +0 -1
- package/dist/chunk-U36WHHML.js.map +0 -1
- package/dist/chunk-WE7SQ7PB.js.map +0 -1
- /package/dist/{chunk-TP4MD4PD.js.map → chunk-EZR72HCQ.js.map} +0 -0
- /package/dist/{chunk-JR2RWYQO.js.map → chunk-I5EF5KIT.js.map} +0 -0
- /package/dist/{chunk-EKSAOR2I.js.map → chunk-Q7TDHVRZ.js.map} +0 -0
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
canonicalIntegrationKind,
|
|
8
8
|
integrationSpecToConnector,
|
|
9
9
|
listIntegrationSpecs
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CBWD2IOL.js";
|
|
11
11
|
import {
|
|
12
12
|
listBundledConnectorAdapters
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PXOM5AVK.js";
|
|
14
14
|
import {
|
|
15
15
|
CONNECTOR_ADAPTER_FACTORIES
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-HNEEIMQC.js";
|
|
17
17
|
|
|
18
18
|
// src/activepieces-catalog.ts
|
|
19
19
|
import { readFileSync } from "fs";
|
|
@@ -208,7 +208,7 @@ function dataClassFor(category) {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// src/index.ts
|
|
211
|
-
import { createHmac, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
|
|
211
|
+
import { createHash as createHash3, createHmac, randomBytes, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
|
|
212
212
|
|
|
213
213
|
// src/core-error.ts
|
|
214
214
|
var IntegrationError = class extends Error {
|
|
@@ -248,7 +248,11 @@ function createConnectorAdapterProvider(options) {
|
|
|
248
248
|
"auth_not_supported"
|
|
249
249
|
);
|
|
250
250
|
}
|
|
251
|
-
|
|
251
|
+
const pkce = resolveOAuthPkceMode(auth.pkce, request.connectorId);
|
|
252
|
+
const tokenClientAuthMethod = resolveTokenClientAuthMethod(
|
|
253
|
+
auth.tokenClientAuthMethod,
|
|
254
|
+
request.connectorId
|
|
255
|
+
);
|
|
252
256
|
if (!options.resolveOAuthClient) {
|
|
253
257
|
throw new IntegrationError(
|
|
254
258
|
`OAuth client resolver missing on adapter provider; cannot start auth for ${request.connectorId}.`,
|
|
@@ -256,7 +260,7 @@ function createConnectorAdapterProvider(options) {
|
|
|
256
260
|
);
|
|
257
261
|
}
|
|
258
262
|
const client = await options.resolveOAuthClient({ connectorId: request.connectorId });
|
|
259
|
-
if (!client || !client.clientId || !client.clientSecret) {
|
|
263
|
+
if (!client || !client.clientId || tokenClientAuthMethod !== "none" && !client.clientSecret) {
|
|
260
264
|
throw new IntegrationError(
|
|
261
265
|
`OAuth client credentials unavailable for ${request.connectorId}.`,
|
|
262
266
|
"config_missing"
|
|
@@ -275,7 +279,14 @@ function createConnectorAdapterProvider(options) {
|
|
|
275
279
|
request.connectorId
|
|
276
280
|
));
|
|
277
281
|
url.searchParams.set("response_type", "code");
|
|
278
|
-
url.searchParams.set(
|
|
282
|
+
url.searchParams.set(
|
|
283
|
+
resolveOAuthParameterName(
|
|
284
|
+
auth.authorizationClientIdParam,
|
|
285
|
+
"client_id",
|
|
286
|
+
request.connectorId
|
|
287
|
+
),
|
|
288
|
+
client.clientId
|
|
289
|
+
);
|
|
279
290
|
url.searchParams.set("redirect_uri", request.redirectUri);
|
|
280
291
|
if (scopes.length > 0) {
|
|
281
292
|
url.searchParams.set("scope", scopes.join(auth.scopeSeparator ?? " "));
|
|
@@ -287,6 +298,16 @@ function createConnectorAdapterProvider(options) {
|
|
|
287
298
|
url.searchParams.set(key, value);
|
|
288
299
|
}
|
|
289
300
|
}
|
|
301
|
+
if (pkce === "unsupported") {
|
|
302
|
+
url.searchParams.delete("code_challenge");
|
|
303
|
+
url.searchParams.delete("code_challenge_method");
|
|
304
|
+
} else {
|
|
305
|
+
url.searchParams.set(
|
|
306
|
+
"code_challenge",
|
|
307
|
+
requirePkceChallenge(request.codeChallenge, request.connectorId)
|
|
308
|
+
);
|
|
309
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
310
|
+
}
|
|
290
311
|
return {
|
|
291
312
|
providerId,
|
|
292
313
|
connectorId: request.connectorId,
|
|
@@ -314,6 +335,7 @@ function createConnectorAdapterProvider(options) {
|
|
|
314
335
|
auth.tokenClientAuthMethod,
|
|
315
336
|
request.connectorId
|
|
316
337
|
);
|
|
338
|
+
const pkce = resolveOAuthPkceMode(auth.pkce, request.connectorId);
|
|
317
339
|
if (!request.code) {
|
|
318
340
|
throw new IntegrationError(
|
|
319
341
|
`Authorization code missing on completeAuth for ${request.connectorId}.`,
|
|
@@ -327,7 +349,7 @@ function createConnectorAdapterProvider(options) {
|
|
|
327
349
|
);
|
|
328
350
|
}
|
|
329
351
|
const client = await options.resolveOAuthClient({ connectorId: request.connectorId });
|
|
330
|
-
if (!client || !client.clientId || !client.clientSecret) {
|
|
352
|
+
if (!client || !client.clientId || tokenClientAuthMethod !== "none" && !client.clientSecret) {
|
|
331
353
|
throw new IntegrationError(
|
|
332
354
|
`OAuth client credentials unavailable for ${request.connectorId}.`,
|
|
333
355
|
"config_missing"
|
|
@@ -344,14 +366,44 @@ function createConnectorAdapterProvider(options) {
|
|
|
344
366
|
code: request.code,
|
|
345
367
|
redirect_uri: request.redirectUri
|
|
346
368
|
});
|
|
369
|
+
const codeVerifier = pkce === "unsupported" ? void 0 : requirePkceVerifier(request.codeVerifier, request.connectorId);
|
|
370
|
+
if (codeVerifier) body.set("code_verifier", codeVerifier);
|
|
347
371
|
const headers = {
|
|
348
372
|
"content-type": "application/x-www-form-urlencoded",
|
|
349
373
|
accept: "application/json"
|
|
350
374
|
};
|
|
351
|
-
const redactionValues = [
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
375
|
+
const redactionValues = [
|
|
376
|
+
request.code,
|
|
377
|
+
client.clientId,
|
|
378
|
+
...client.clientSecret ? [client.clientSecret] : [],
|
|
379
|
+
...codeVerifier ? [codeVerifier] : []
|
|
380
|
+
];
|
|
381
|
+
if (tokenClientAuthMethod === "none") {
|
|
382
|
+
body.set(
|
|
383
|
+
resolveOAuthParameterName(
|
|
384
|
+
auth.tokenClientIdParam,
|
|
385
|
+
"client_id",
|
|
386
|
+
request.connectorId
|
|
387
|
+
),
|
|
388
|
+
client.clientId
|
|
389
|
+
);
|
|
390
|
+
} else if (tokenClientAuthMethod === "client_secret_post") {
|
|
391
|
+
body.set(
|
|
392
|
+
resolveOAuthParameterName(
|
|
393
|
+
auth.tokenClientIdParam,
|
|
394
|
+
"client_id",
|
|
395
|
+
request.connectorId
|
|
396
|
+
),
|
|
397
|
+
client.clientId
|
|
398
|
+
);
|
|
399
|
+
body.set(
|
|
400
|
+
resolveOAuthParameterName(
|
|
401
|
+
auth.tokenClientSecretParam,
|
|
402
|
+
"client_secret",
|
|
403
|
+
request.connectorId
|
|
404
|
+
),
|
|
405
|
+
client.clientSecret
|
|
406
|
+
);
|
|
355
407
|
} else {
|
|
356
408
|
const authorization = `Basic ${base64Encode(`${client.clientId}:${client.clientSecret}`)}`;
|
|
357
409
|
headers.authorization = authorization;
|
|
@@ -433,7 +485,16 @@ function createConnectorAdapterProvider(options) {
|
|
|
433
485
|
throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
|
|
434
486
|
}
|
|
435
487
|
const source = await options.resolveDataSource(connection);
|
|
436
|
-
let
|
|
488
|
+
let rotationPersistence = Promise.resolve();
|
|
489
|
+
const persistRotation = (credentials) => {
|
|
490
|
+
if (!options.onCredentialsRotated) return Promise.resolve();
|
|
491
|
+
rotationPersistence = rotationPersistence.then(
|
|
492
|
+
() => options.onCredentialsRotated({ connection, credentials })
|
|
493
|
+
);
|
|
494
|
+
void rotationPersistence.catch(() => {
|
|
495
|
+
});
|
|
496
|
+
return rotationPersistence;
|
|
497
|
+
};
|
|
437
498
|
const invocation = {
|
|
438
499
|
source,
|
|
439
500
|
capabilityName: request.action,
|
|
@@ -441,32 +502,27 @@ function createConnectorAdapterProvider(options) {
|
|
|
441
502
|
idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
|
|
442
503
|
expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
|
|
443
504
|
callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0,
|
|
444
|
-
onCredentialsRotated: options.onCredentialsRotated ?
|
|
445
|
-
rotated = credentials;
|
|
446
|
-
} : void 0
|
|
447
|
-
};
|
|
448
|
-
const persistRotation = async () => {
|
|
449
|
-
if (rotated && options.onCredentialsRotated) {
|
|
450
|
-
await options.onCredentialsRotated({ connection, credentials: rotated });
|
|
451
|
-
}
|
|
505
|
+
onCredentialsRotated: options.onCredentialsRotated ? persistRotation : void 0
|
|
452
506
|
};
|
|
453
|
-
|
|
454
|
-
if (
|
|
455
|
-
|
|
507
|
+
try {
|
|
508
|
+
if (capability.class === "read") {
|
|
509
|
+
if (!adapter.executeRead) {
|
|
510
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
|
|
511
|
+
}
|
|
512
|
+
const result = await adapter.executeRead(invocation);
|
|
513
|
+
return readResultToAction(request, result);
|
|
456
514
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
|
|
515
|
+
if (capability.class === "mutation") {
|
|
516
|
+
if (!adapter.executeMutation) {
|
|
517
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
|
|
518
|
+
}
|
|
519
|
+
const result = await adapter.executeMutation(invocation);
|
|
520
|
+
return mutationResultToAction(request, result);
|
|
464
521
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
522
|
+
throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
|
|
523
|
+
} finally {
|
|
524
|
+
await rotationPersistence;
|
|
468
525
|
}
|
|
469
|
-
throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
|
|
470
526
|
}
|
|
471
527
|
};
|
|
472
528
|
}
|
|
@@ -484,6 +540,7 @@ function createConnectorAdapterCatalogSource(options) {
|
|
|
484
540
|
function manifestToConnector(providerId, adapter) {
|
|
485
541
|
const manifest = adapter.manifest;
|
|
486
542
|
const primaryAuth = primaryManifestAuth(manifest.auth);
|
|
543
|
+
const oauthAuth = oauthManifestAuth(manifest.auth);
|
|
487
544
|
return {
|
|
488
545
|
id: manifest.kind,
|
|
489
546
|
providerId,
|
|
@@ -505,6 +562,7 @@ function manifestToConnector(providerId, adapter) {
|
|
|
505
562
|
source: "first-party-adapter",
|
|
506
563
|
supportTier: "firstPartyExecutable",
|
|
507
564
|
executable: true,
|
|
565
|
+
oauthPkce: oauthAuth ? resolveOAuthPkceMode(oauthAuth.pkce, manifest.kind) : void 0,
|
|
508
566
|
authOptions: manifest.auth.kind === "one_of" ? manifest.auth.options.map((auth) => auth.kind) : [manifest.auth.kind],
|
|
509
567
|
preferredAuth: manifest.auth.kind === "one_of" ? manifest.auth.preferred : manifest.auth.kind
|
|
510
568
|
}
|
|
@@ -576,12 +634,48 @@ function resolveTokenClientAuthMethod(value, connectorId) {
|
|
|
576
634
|
if (value === void 0 || value === "client_secret_post") {
|
|
577
635
|
return "client_secret_post";
|
|
578
636
|
}
|
|
579
|
-
if (value === "client_secret_basic") return value;
|
|
637
|
+
if (value === "none" || value === "client_secret_basic") return value;
|
|
580
638
|
throw new IntegrationError(
|
|
581
639
|
`Connector ${connectorId} has an unsupported OAuth token client authentication method.`,
|
|
582
640
|
"config_missing"
|
|
583
641
|
);
|
|
584
642
|
}
|
|
643
|
+
function resolveOAuthPkceMode(value, connectorId) {
|
|
644
|
+
if (value === void 0 || value === "required") return "required";
|
|
645
|
+
if (value === "supported" || value === "unsupported") return value;
|
|
646
|
+
throw new IntegrationError(
|
|
647
|
+
`Connector ${connectorId} has an invalid OAuth PKCE posture.`,
|
|
648
|
+
"config_missing"
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
function requirePkceChallenge(value, connectorId) {
|
|
652
|
+
if (!value || !/^[A-Za-z0-9_-]{43}$/.test(value)) {
|
|
653
|
+
throw new IntegrationError(
|
|
654
|
+
`OAuth PKCE S256 challenge missing or invalid for ${connectorId}.`,
|
|
655
|
+
"config_missing"
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return value;
|
|
659
|
+
}
|
|
660
|
+
function requirePkceVerifier(value, connectorId) {
|
|
661
|
+
if (!value || !/^[A-Za-z0-9._~-]{43,128}$/.test(value)) {
|
|
662
|
+
throw new IntegrationError(
|
|
663
|
+
`OAuth PKCE verifier missing or invalid for ${connectorId}.`,
|
|
664
|
+
"config_missing"
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
return value;
|
|
668
|
+
}
|
|
669
|
+
function resolveOAuthParameterName(value, fallback, connectorId) {
|
|
670
|
+
const name = value ?? fallback;
|
|
671
|
+
if (!/^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(name)) {
|
|
672
|
+
throw new IntegrationError(
|
|
673
|
+
`Connector ${connectorId} declares an invalid OAuth parameter name.`,
|
|
674
|
+
"config_missing"
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
return name;
|
|
678
|
+
}
|
|
585
679
|
function base64Encode(value) {
|
|
586
680
|
const btoa2 = globalThis.btoa;
|
|
587
681
|
if (typeof btoa2 !== "function") {
|
|
@@ -698,6 +792,15 @@ function base64UrlEncode(bytes) {
|
|
|
698
792
|
}
|
|
699
793
|
|
|
700
794
|
// src/credentials.ts
|
|
795
|
+
var CredentialRefreshRevokedError = class extends Error {
|
|
796
|
+
constructor() {
|
|
797
|
+
super("Connection was revoked during credential refresh.");
|
|
798
|
+
this.name = "CredentialRefreshRevokedError";
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
var credentialRefreshes = /* @__PURE__ */ new WeakMap();
|
|
802
|
+
var credentialPersistences = /* @__PURE__ */ new WeakMap();
|
|
803
|
+
var credentialRevocations = /* @__PURE__ */ new WeakMap();
|
|
701
804
|
var InMemoryIntegrationSecretStore = class {
|
|
702
805
|
secrets = /* @__PURE__ */ new Map();
|
|
703
806
|
get(ref) {
|
|
@@ -755,35 +858,141 @@ function createConnectionCredentialResolver(options) {
|
|
|
755
858
|
async function resolveConnectionCredentials(input, options) {
|
|
756
859
|
if (input.status !== "active") throw new Error(`Connection ${input.id} is ${input.status}.`);
|
|
757
860
|
if (!input.secretRef) return { kind: "none" };
|
|
861
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
758
862
|
const current = await options.secrets.get(input.secretRef);
|
|
759
863
|
if (!current) throw new Error(`Secret ${input.secretRef.provider}/${input.secretRef.id} not found.`);
|
|
760
|
-
if (!isExpiredOauth(current,
|
|
864
|
+
if (!isExpiredOauth(current, now)) return current;
|
|
761
865
|
const adapter = options.adapters?.find((candidate) => candidate.manifest.kind === input.connectorId);
|
|
762
866
|
if (!adapter?.refreshToken) return current;
|
|
867
|
+
let refresh;
|
|
763
868
|
try {
|
|
764
|
-
|
|
765
|
-
|
|
869
|
+
await assertCredentialRefreshActive(input, options, now);
|
|
870
|
+
refresh = acquireCredentialRefresh({
|
|
871
|
+
connectorId: input.connectorId,
|
|
872
|
+
secretRef: input.secretRef,
|
|
873
|
+
secrets: options.secrets,
|
|
874
|
+
now,
|
|
875
|
+
refreshToken: adapter.refreshToken.bind(adapter)
|
|
876
|
+
});
|
|
877
|
+
const refreshed = await refresh.promise;
|
|
878
|
+
await assertCredentialRefreshActive(input, options, now, refresh);
|
|
766
879
|
if (options.connections) {
|
|
767
880
|
await options.connections.put({
|
|
768
881
|
...input,
|
|
769
882
|
status: "active",
|
|
770
|
-
updatedAt:
|
|
883
|
+
updatedAt: now().toISOString(),
|
|
771
884
|
expiresAt: refreshed.kind === "oauth2" && refreshed.expiresAt ? new Date(refreshed.expiresAt).toISOString() : input.expiresAt
|
|
772
885
|
});
|
|
886
|
+
await assertCredentialRefreshActive(input, options, now, refresh);
|
|
773
887
|
}
|
|
774
888
|
return refreshed;
|
|
775
889
|
} catch (error) {
|
|
776
890
|
const err = error instanceof Error ? error : new Error("Credential refresh failed.");
|
|
891
|
+
if (err instanceof CredentialRefreshRevokedError) throw err;
|
|
777
892
|
await options.markConnectionError?.(input, err);
|
|
778
893
|
if (options.connections) {
|
|
779
894
|
await options.connections.put({
|
|
780
895
|
...input,
|
|
781
896
|
status: "expired",
|
|
782
|
-
updatedAt:
|
|
897
|
+
updatedAt: now().toISOString()
|
|
783
898
|
});
|
|
784
899
|
}
|
|
785
900
|
throw err;
|
|
901
|
+
} finally {
|
|
902
|
+
if (refresh) {
|
|
903
|
+
releaseCredentialRefresh(
|
|
904
|
+
options.secrets,
|
|
905
|
+
input.connectorId,
|
|
906
|
+
input.secretRef,
|
|
907
|
+
refresh
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function acquireCredentialRefresh(input) {
|
|
913
|
+
let refreshes = credentialRefreshes.get(input.secrets);
|
|
914
|
+
if (!refreshes) {
|
|
915
|
+
refreshes = /* @__PURE__ */ new Map();
|
|
916
|
+
credentialRefreshes.set(input.secrets, refreshes);
|
|
917
|
+
}
|
|
918
|
+
const key = credentialRefreshKey(input.connectorId, input.secretRef);
|
|
919
|
+
if (credentialRevocations.get(input.secrets)?.has(key)) {
|
|
920
|
+
throw new CredentialRefreshRevokedError();
|
|
921
|
+
}
|
|
922
|
+
const existing = refreshes.get(key);
|
|
923
|
+
if (existing) {
|
|
924
|
+
existing.waiters += 1;
|
|
925
|
+
return existing;
|
|
926
|
+
}
|
|
927
|
+
let flight;
|
|
928
|
+
const promise = Promise.resolve().then(async () => {
|
|
929
|
+
const latest = await input.secrets.get(input.secretRef);
|
|
930
|
+
if (!latest) {
|
|
931
|
+
throw new Error(
|
|
932
|
+
`Secret ${input.secretRef.provider}/${input.secretRef.id} not found.`
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
if (!isExpiredOauth(latest, input.now)) return latest;
|
|
936
|
+
const refreshed = await input.refreshToken(latest);
|
|
937
|
+
if (flight.cancelled) throw new CredentialRefreshRevokedError();
|
|
938
|
+
await input.secrets.put(input.secretRef, refreshed);
|
|
939
|
+
if (flight.cancelled) {
|
|
940
|
+
await input.secrets.delete?.(input.secretRef);
|
|
941
|
+
throw new CredentialRefreshRevokedError();
|
|
942
|
+
}
|
|
943
|
+
return refreshed;
|
|
944
|
+
});
|
|
945
|
+
flight = {
|
|
946
|
+
promise,
|
|
947
|
+
cancelled: false,
|
|
948
|
+
settled: false,
|
|
949
|
+
waiters: 1
|
|
950
|
+
};
|
|
951
|
+
refreshes.set(key, flight);
|
|
952
|
+
const settle = () => {
|
|
953
|
+
flight.settled = true;
|
|
954
|
+
clearCredentialRefresh(input.secrets, key, flight);
|
|
955
|
+
};
|
|
956
|
+
void promise.then(settle, settle);
|
|
957
|
+
return flight;
|
|
958
|
+
}
|
|
959
|
+
function releaseCredentialRefresh(secrets, connectorId, secretRef, flight) {
|
|
960
|
+
flight.waiters -= 1;
|
|
961
|
+
clearCredentialRefresh(
|
|
962
|
+
secrets,
|
|
963
|
+
credentialRefreshKey(connectorId, secretRef),
|
|
964
|
+
flight
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
function clearCredentialRefresh(secrets, key, flight) {
|
|
968
|
+
if (!flight.settled || flight.waiters !== 0) return;
|
|
969
|
+
const refreshes = credentialRefreshes.get(secrets);
|
|
970
|
+
if (refreshes?.get(key) !== flight) return;
|
|
971
|
+
refreshes.delete(key);
|
|
972
|
+
if (refreshes.size === 0) credentialRefreshes.delete(secrets);
|
|
973
|
+
}
|
|
974
|
+
async function assertCredentialRefreshActive(input, options, now, operation) {
|
|
975
|
+
const key = input.secretRef ? credentialRefreshKey(input.connectorId, input.secretRef) : void 0;
|
|
976
|
+
let revoked = operation?.cancelled === true || key !== void 0 && credentialRevocations.get(options.secrets)?.has(key) === true;
|
|
977
|
+
let stored;
|
|
978
|
+
if (options.connections) {
|
|
979
|
+
stored = await options.connections.get(input.id);
|
|
980
|
+
revoked ||= stored?.status === "revoked";
|
|
981
|
+
}
|
|
982
|
+
if (!revoked && input.secretRef) {
|
|
983
|
+
revoked = await options.secrets.get(input.secretRef) === void 0;
|
|
984
|
+
}
|
|
985
|
+
if (!revoked) return;
|
|
986
|
+
if (operation) operation.cancelled = true;
|
|
987
|
+
if (input.secretRef) await options.secrets.delete?.(input.secretRef);
|
|
988
|
+
if (options.connections && stored?.status !== "revoked") {
|
|
989
|
+
await options.connections.put({
|
|
990
|
+
...stored ?? input,
|
|
991
|
+
status: "revoked",
|
|
992
|
+
updatedAt: now().toISOString()
|
|
993
|
+
});
|
|
786
994
|
}
|
|
995
|
+
throw new CredentialRefreshRevokedError();
|
|
787
996
|
}
|
|
788
997
|
function createCredentialBackedAdapterProvider(options) {
|
|
789
998
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -791,30 +1000,106 @@ function createCredentialBackedAdapterProvider(options) {
|
|
|
791
1000
|
...options,
|
|
792
1001
|
resolveDataSource: createConnectionCredentialResolver(options),
|
|
793
1002
|
onCredentialsRotated: async ({ connection, credentials }) => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
1003
|
+
const persistence = connection.secretRef ? acquireCredentialPersistence(
|
|
1004
|
+
options.secrets,
|
|
1005
|
+
connection.connectorId,
|
|
1006
|
+
connection.secretRef
|
|
1007
|
+
) : void 0;
|
|
1008
|
+
try {
|
|
1009
|
+
await assertCredentialRefreshActive(connection, options, now, persistence);
|
|
1010
|
+
if (connection.secretRef) {
|
|
1011
|
+
await options.secrets.put(connection.secretRef, credentials);
|
|
1012
|
+
}
|
|
1013
|
+
await assertCredentialRefreshActive(connection, options, now, persistence);
|
|
1014
|
+
if (options.connections) {
|
|
1015
|
+
await options.connections.put({
|
|
1016
|
+
...connection,
|
|
1017
|
+
status: "active",
|
|
1018
|
+
updatedAt: now().toISOString(),
|
|
1019
|
+
expiresAt: credentials.kind === "oauth2" && credentials.expiresAt ? new Date(credentials.expiresAt).toISOString() : connection.expiresAt
|
|
1020
|
+
});
|
|
1021
|
+
await assertCredentialRefreshActive(connection, options, now, persistence);
|
|
1022
|
+
}
|
|
1023
|
+
await options.onCredentialsRotated?.({ connection, secretRef: connection.secretRef, credentials });
|
|
1024
|
+
await assertCredentialRefreshActive(connection, options, now, persistence);
|
|
1025
|
+
} finally {
|
|
1026
|
+
if (persistence && connection.secretRef) {
|
|
1027
|
+
releaseCredentialPersistence(
|
|
1028
|
+
options.secrets,
|
|
1029
|
+
connection.connectorId,
|
|
1030
|
+
connection.secretRef,
|
|
1031
|
+
persistence
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
804
1034
|
}
|
|
805
|
-
await options.onCredentialsRotated?.({ connection, secretRef: connection.secretRef, credentials });
|
|
806
1035
|
}
|
|
807
1036
|
});
|
|
808
1037
|
}
|
|
1038
|
+
function acquireCredentialPersistence(secrets, connectorId, secretRef) {
|
|
1039
|
+
let persistences = credentialPersistences.get(secrets);
|
|
1040
|
+
if (!persistences) {
|
|
1041
|
+
persistences = /* @__PURE__ */ new Map();
|
|
1042
|
+
credentialPersistences.set(secrets, persistences);
|
|
1043
|
+
}
|
|
1044
|
+
const key = credentialRefreshKey(connectorId, secretRef);
|
|
1045
|
+
let leases = persistences.get(key);
|
|
1046
|
+
if (!leases) {
|
|
1047
|
+
leases = /* @__PURE__ */ new Set();
|
|
1048
|
+
persistences.set(key, leases);
|
|
1049
|
+
}
|
|
1050
|
+
const lease = {
|
|
1051
|
+
cancelled: credentialRevocations.get(secrets)?.has(key) === true
|
|
1052
|
+
};
|
|
1053
|
+
leases.add(lease);
|
|
1054
|
+
return lease;
|
|
1055
|
+
}
|
|
1056
|
+
function releaseCredentialPersistence(secrets, connectorId, secretRef, lease) {
|
|
1057
|
+
const persistences = credentialPersistences.get(secrets);
|
|
1058
|
+
if (!persistences) return;
|
|
1059
|
+
const key = credentialRefreshKey(connectorId, secretRef);
|
|
1060
|
+
const leases = persistences.get(key);
|
|
1061
|
+
leases?.delete(lease);
|
|
1062
|
+
if (leases?.size === 0) persistences.delete(key);
|
|
1063
|
+
if (persistences.size === 0) credentialPersistences.delete(secrets);
|
|
1064
|
+
}
|
|
809
1065
|
async function revokeConnection(input) {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1066
|
+
const finishRevocation = input.connection.secretRef && input.secrets ? beginCredentialRevocation(
|
|
1067
|
+
input.secrets,
|
|
1068
|
+
input.connection.connectorId,
|
|
1069
|
+
input.connection.secretRef
|
|
1070
|
+
) : void 0;
|
|
1071
|
+
try {
|
|
1072
|
+
if (input.connection.secretRef) await input.secrets?.delete?.(input.connection.secretRef);
|
|
1073
|
+
const revoked = {
|
|
1074
|
+
...input.connection,
|
|
1075
|
+
status: "revoked",
|
|
1076
|
+
updatedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
1077
|
+
};
|
|
1078
|
+
await input.connections?.put(revoked);
|
|
1079
|
+
return revoked;
|
|
1080
|
+
} finally {
|
|
1081
|
+
finishRevocation?.();
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function beginCredentialRevocation(secrets, connectorId, secretRef) {
|
|
1085
|
+
const key = credentialRefreshKey(connectorId, secretRef);
|
|
1086
|
+
let revocations = credentialRevocations.get(secrets);
|
|
1087
|
+
if (!revocations) {
|
|
1088
|
+
revocations = /* @__PURE__ */ new Map();
|
|
1089
|
+
credentialRevocations.set(secrets, revocations);
|
|
1090
|
+
}
|
|
1091
|
+
revocations.set(key, (revocations.get(key) ?? 0) + 1);
|
|
1092
|
+
const flight = credentialRefreshes.get(secrets)?.get(key);
|
|
1093
|
+
if (flight) flight.cancelled = true;
|
|
1094
|
+
for (const persistence of credentialPersistences.get(secrets)?.get(key) ?? []) {
|
|
1095
|
+
persistence.cancelled = true;
|
|
1096
|
+
}
|
|
1097
|
+
return () => {
|
|
1098
|
+
const remaining = (revocations?.get(key) ?? 1) - 1;
|
|
1099
|
+
if (remaining > 0) revocations?.set(key, remaining);
|
|
1100
|
+
else revocations?.delete(key);
|
|
1101
|
+
if (revocations?.size === 0) credentialRevocations.delete(secrets);
|
|
815
1102
|
};
|
|
816
|
-
await input.connections?.put(revoked);
|
|
817
|
-
return revoked;
|
|
818
1103
|
}
|
|
819
1104
|
function isExpiredOauth(credentials, now) {
|
|
820
1105
|
return credentials.kind === "oauth2" && typeof credentials.expiresAt === "number" && credentials.expiresAt <= now().getTime() && Boolean(credentials.refreshToken);
|
|
@@ -822,6 +1107,9 @@ function isExpiredOauth(credentials, now) {
|
|
|
822
1107
|
function secretKey(ref) {
|
|
823
1108
|
return `${ref.provider}:${ref.id}`;
|
|
824
1109
|
}
|
|
1110
|
+
function credentialRefreshKey(connectorId, ref) {
|
|
1111
|
+
return JSON.stringify([connectorId, ref.provider, ref.id]);
|
|
1112
|
+
}
|
|
825
1113
|
|
|
826
1114
|
// src/audit.ts
|
|
827
1115
|
import { randomUUID } from "crypto";
|
|
@@ -3302,8 +3590,13 @@ var IntegrationHub = class {
|
|
|
3302
3590
|
async startAuth(providerId, request) {
|
|
3303
3591
|
const provider = this.requireProvider(providerId);
|
|
3304
3592
|
if (!provider.startAuth) throw new IntegrationError(`Provider ${providerId} does not support auth start.`, "auth_not_supported");
|
|
3305
|
-
await this.requireConnector(provider, request.connectorId);
|
|
3306
|
-
const
|
|
3593
|
+
const connector = await this.requireConnector(provider, request.connectorId);
|
|
3594
|
+
const pkce = connectorOAuthPkceMode(connector);
|
|
3595
|
+
const pkcePair = pkce === "unsupported" ? void 0 : createPkcePair();
|
|
3596
|
+
const result = await provider.startAuth({
|
|
3597
|
+
...request,
|
|
3598
|
+
codeChallenge: pkcePair?.challenge
|
|
3599
|
+
});
|
|
3307
3600
|
const record = {
|
|
3308
3601
|
state: result.state,
|
|
3309
3602
|
providerId,
|
|
@@ -3311,6 +3604,7 @@ var IntegrationHub = class {
|
|
|
3311
3604
|
owner: request.owner,
|
|
3312
3605
|
requestedScopes: request.requestedScopes,
|
|
3313
3606
|
redirectUri: request.redirectUri,
|
|
3607
|
+
codeVerifier: pkcePair?.verifier,
|
|
3314
3608
|
expiresAt: this.now().getTime() + this.oauthStateTtlMs,
|
|
3315
3609
|
metadata: request.metadata
|
|
3316
3610
|
};
|
|
@@ -3330,7 +3624,16 @@ var IntegrationHub = class {
|
|
|
3330
3624
|
if (outcome.state.redirectUri !== request.redirectUri) {
|
|
3331
3625
|
throw new IntegrationError("Integration OAuth redirect URI does not match the start request.", "capability_invalid");
|
|
3332
3626
|
}
|
|
3333
|
-
const
|
|
3627
|
+
const pinnedMetadata = request.metadata || outcome.state.metadata ? { ...request.metadata, ...outcome.state.metadata } : void 0;
|
|
3628
|
+
const connection = await provider.completeAuth({
|
|
3629
|
+
...request,
|
|
3630
|
+
connectorId: outcome.state.connectorId,
|
|
3631
|
+
owner: outcome.state.owner,
|
|
3632
|
+
state: outcome.state.state,
|
|
3633
|
+
redirectUri: outcome.state.redirectUri,
|
|
3634
|
+
codeVerifier: outcome.state.codeVerifier,
|
|
3635
|
+
metadata: pinnedMetadata
|
|
3636
|
+
});
|
|
3334
3637
|
await this.store.put(connection);
|
|
3335
3638
|
return connection;
|
|
3336
3639
|
}
|
|
@@ -3587,6 +3890,20 @@ function assertScopes(connection, requiredScopes2) {
|
|
|
3587
3890
|
const missing2 = requiredScopes2.filter((scope) => !connection.grantedScopes.includes(scope));
|
|
3588
3891
|
if (missing2.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing2.join(", ")}`, "scope_denied");
|
|
3589
3892
|
}
|
|
3893
|
+
function connectorOAuthPkceMode(connector) {
|
|
3894
|
+
const value = connector.metadata?.oauthPkce;
|
|
3895
|
+
if (value === void 0) return "required";
|
|
3896
|
+
if (value === "required" || value === "supported" || value === "unsupported") return value;
|
|
3897
|
+
throw new IntegrationError(
|
|
3898
|
+
`Connector ${connector.id} declares an invalid OAuth PKCE posture.`,
|
|
3899
|
+
"config_missing"
|
|
3900
|
+
);
|
|
3901
|
+
}
|
|
3902
|
+
function createPkcePair() {
|
|
3903
|
+
const verifier = randomBytes(48).toString("base64url");
|
|
3904
|
+
const challenge = createHash3("sha256").update(verifier).digest("base64url");
|
|
3905
|
+
return { verifier, challenge };
|
|
3906
|
+
}
|
|
3590
3907
|
function hmac(payload, secret) {
|
|
3591
3908
|
return createHmac("sha256", secret).update(payload).digest("base64url");
|
|
3592
3909
|
}
|
|
@@ -4426,4 +4743,4 @@ export {
|
|
|
4426
4743
|
signCapability,
|
|
4427
4744
|
verifyCapabilityToken
|
|
4428
4745
|
};
|
|
4429
|
-
//# sourceMappingURL=chunk-
|
|
4746
|
+
//# sourceMappingURL=chunk-GY4PFAWA.js.map
|