@tangle-network/agent-integrations 0.53.46 → 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.
Files changed (54) hide show
  1. package/dist/catalog.d.ts +2 -2
  2. package/dist/catalog.js +9 -9
  3. package/dist/{chunk-AAIEUJSS.js → chunk-CBWD2IOL.js} +251 -10
  4. package/dist/chunk-CBWD2IOL.js.map +1 -0
  5. package/dist/{chunk-TP4MD4PD.js → chunk-EZR72HCQ.js} +2 -2
  6. package/dist/{chunk-ATIOKZO5.js → chunk-GY4PFAWA.js} +380 -63
  7. package/dist/chunk-GY4PFAWA.js.map +1 -0
  8. package/dist/{chunk-CMNY7MMC.js → chunk-HNEEIMQC.js} +2732 -500
  9. package/dist/{chunk-CMNY7MMC.js.map → chunk-HNEEIMQC.js.map} +1 -1
  10. package/dist/{chunk-JR2RWYQO.js → chunk-I5EF5KIT.js} +2 -2
  11. package/dist/{chunk-7UEPOLGS.js → chunk-PXOM5AVK.js} +7 -2
  12. package/dist/{chunk-7UEPOLGS.js.map → chunk-PXOM5AVK.js.map} +1 -1
  13. package/dist/{chunk-EKSAOR2I.js → chunk-Q7TDHVRZ.js} +2 -2
  14. package/dist/{chunk-WE7SQ7PB.js → chunk-RRSNINEV.js} +15 -3
  15. package/dist/chunk-RRSNINEV.js.map +1 -0
  16. package/dist/{chunk-AG4ORH2A.js → chunk-TKEVCGAI.js} +6 -6
  17. package/dist/chunk-TKEVCGAI.js.map +1 -0
  18. package/dist/connect/index.d.ts +2 -2
  19. package/dist/connect/index.js +2 -2
  20. package/dist/connectors/adapters/index.d.ts +50 -3
  21. package/dist/connectors/adapters/index.js +18 -2
  22. package/dist/connectors/index.d.ts +37 -12
  23. package/dist/connectors/index.js +19 -3
  24. package/dist/{consumer-UnsjK9iM.d.ts → consumer-BcFIDgMD.d.ts} +1 -1
  25. package/dist/consumer.d.ts +3 -3
  26. package/dist/consumer.js +2 -2
  27. package/dist/{core-types-Cx53j2Xp.d.ts → core-types-CE1oMiuI.d.ts} +7 -1
  28. package/dist/coverage-catalog.d.ts +2 -2
  29. package/dist/coverage-catalog.js +1 -1
  30. package/dist/{importers-YLXwPCz_.d.ts → importers-CHOC-2Y8.d.ts} +1 -1
  31. package/dist/index.d.ts +13 -12
  32. package/dist/index.js +25 -9
  33. package/dist/mcp.d.ts +3 -3
  34. package/dist/mcp.js +9 -9
  35. package/dist/middleware/index.d.ts +2 -2
  36. package/dist/middleware/index.js +2 -2
  37. package/dist/registry.d.ts +2 -2
  38. package/dist/registry.js +9 -9
  39. package/dist/runtime.d.ts +2 -2
  40. package/dist/runtime.js +9 -9
  41. package/dist/specs.d.ts +11 -3
  42. package/dist/specs.js +5 -5
  43. package/dist/{tangle-id--7mgjNkq.d.ts → tangle-id-BL_JQwKA.d.ts} +1 -1
  44. package/dist/{types-qFsboS2G.d.ts → types-EXGqQYoU.d.ts} +25 -10
  45. package/docs/integration-execution-audit.md +17 -11
  46. package/docs/integration-execution-matrix.json +837 -304
  47. package/package.json +1 -1
  48. package/dist/chunk-AAIEUJSS.js.map +0 -1
  49. package/dist/chunk-AG4ORH2A.js.map +0 -1
  50. package/dist/chunk-ATIOKZO5.js.map +0 -1
  51. package/dist/chunk-WE7SQ7PB.js.map +0 -1
  52. /package/dist/{chunk-TP4MD4PD.js.map → chunk-EZR72HCQ.js.map} +0 -0
  53. /package/dist/{chunk-JR2RWYQO.js.map → chunk-I5EF5KIT.js.map} +0 -0
  54. /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-AAIEUJSS.js";
10
+ } from "./chunk-CBWD2IOL.js";
11
11
  import {
12
12
  listBundledConnectorAdapters
13
- } from "./chunk-7UEPOLGS.js";
13
+ } from "./chunk-PXOM5AVK.js";
14
14
  import {
15
15
  CONNECTOR_ADAPTER_FACTORIES
16
- } from "./chunk-CMNY7MMC.js";
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
- resolveTokenClientAuthMethod(auth.tokenClientAuthMethod, request.connectorId);
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("client_id", client.clientId);
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 = [request.code, client.clientId, client.clientSecret];
352
- if (tokenClientAuthMethod === "client_secret_post") {
353
- body.set("client_id", client.clientId);
354
- body.set("client_secret", client.clientSecret);
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 rotated;
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 ? (credentials) => {
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
- if (capability.class === "read") {
454
- if (!adapter.executeRead) {
455
- throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
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
- const result = await adapter.executeRead(invocation);
458
- await persistRotation();
459
- return readResultToAction(request, result);
460
- }
461
- if (capability.class === "mutation") {
462
- if (!adapter.executeMutation) {
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
- const result = await adapter.executeMutation(invocation);
466
- await persistRotation();
467
- return mutationResultToAction(request, result);
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, options.now ?? (() => /* @__PURE__ */ new Date()))) return 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
- const refreshed = await adapter.refreshToken(current);
765
- await options.secrets.put(input.secretRef, refreshed);
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: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
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: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
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
- if (connection.secretRef) {
795
- await options.secrets.put(connection.secretRef, credentials);
796
- }
797
- if (options.connections) {
798
- await options.connections.put({
799
- ...connection,
800
- status: "active",
801
- updatedAt: now().toISOString(),
802
- expiresAt: credentials.kind === "oauth2" && credentials.expiresAt ? new Date(credentials.expiresAt).toISOString() : connection.expiresAt
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
- if (input.connection.secretRef) await input.secrets?.delete?.(input.connection.secretRef);
811
- const revoked = {
812
- ...input.connection,
813
- status: "revoked",
814
- updatedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
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 result = await provider.startAuth(request);
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 connection = await provider.completeAuth(request);
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-ATIOKZO5.js.map
4746
+ //# sourceMappingURL=chunk-GY4PFAWA.js.map