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