@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.
Files changed (54) hide show
  1. package/dist/catalog.d.ts +2 -2
  2. package/dist/catalog.js +9 -9
  3. package/dist/{chunk-DHF5EXLO.js → chunk-5UCHLQ3D.js} +9 -3
  4. package/dist/{chunk-DHF5EXLO.js.map → chunk-5UCHLQ3D.js.map} +1 -1
  5. package/dist/{chunk-C7SE5X44.js → chunk-GUZA3U6T.js} +383 -68
  6. package/dist/chunk-GUZA3U6T.js.map +1 -0
  7. package/dist/{chunk-AGXTCR34.js → chunk-H5UCSRF4.js} +2682 -259
  8. package/dist/{chunk-AGXTCR34.js.map → chunk-H5UCSRF4.js.map} +1 -1
  9. package/dist/{chunk-TP4MD4PD.js → chunk-KBYYTHXQ.js} +2 -2
  10. package/dist/{chunk-AG4ORH2A.js → chunk-TKEVCGAI.js} +6 -6
  11. package/dist/chunk-TKEVCGAI.js.map +1 -0
  12. package/dist/{chunk-JR2RWYQO.js → chunk-UI3U62H6.js} +2 -2
  13. package/dist/{chunk-WE7SQ7PB.js → chunk-W72KPXXX.js} +23 -3
  14. package/dist/chunk-W72KPXXX.js.map +1 -0
  15. package/dist/{chunk-U36WHHML.js → chunk-ZSXRS7D7.js} +251 -10
  16. package/dist/chunk-ZSXRS7D7.js.map +1 -0
  17. package/dist/{chunk-EKSAOR2I.js → chunk-ZZA4HOBE.js} +2 -2
  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 +58 -19
  21. package/dist/connectors/adapters/index.js +18 -2
  22. package/dist/connectors/index.d.ts +45 -12
  23. package/dist/connectors/index.js +21 -3
  24. package/dist/{consumer-UnsjK9iM.d.ts → consumer-DrLnbqYt.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-D26cY5tD.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-CQKts3pm.d.ts} +1 -1
  31. package/dist/index.d.ts +13 -12
  32. package/dist/index.js +27 -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-BCOVbWrP.d.ts} +1 -1
  44. package/dist/{types-qFsboS2G.d.ts → types-DpKn0frG.d.ts} +29 -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-AG4ORH2A.js.map +0 -1
  49. package/dist/chunk-C7SE5X44.js.map +0 -1
  50. package/dist/chunk-U36WHHML.js.map +0 -1
  51. package/dist/chunk-WE7SQ7PB.js.map +0 -1
  52. /package/dist/{chunk-TP4MD4PD.js.map → chunk-KBYYTHXQ.js.map} +0 -0
  53. /package/dist/{chunk-JR2RWYQO.js.map → chunk-UI3U62H6.js.map} +0 -0
  54. /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-U36WHHML.js";
10
+ } from "./chunk-ZSXRS7D7.js";
11
11
  import {
12
12
  listBundledConnectorAdapters
13
- } from "./chunk-DHF5EXLO.js";
13
+ } from "./chunk-5UCHLQ3D.js";
14
14
  import {
15
- CONNECTOR_ADAPTER_FACTORIES
16
- } from "./chunk-AGXTCR34.js";
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
- resolveTokenClientAuthMethod(auth.tokenClientAuthMethod, request.connectorId);
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("client_id", client.clientId);
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 headers = {
348
- "content-type": "application/x-www-form-urlencoded",
349
- accept: "application/json"
350
- };
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);
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 rotated;
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 ? (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
- }
503
+ onCredentialsRotated: options.onCredentialsRotated ? persistRotation : void 0
452
504
  };
453
- if (capability.class === "read") {
454
- if (!adapter.executeRead) {
455
- throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
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
- 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");
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
- const result = await adapter.executeMutation(invocation);
466
- await persistRotation();
467
- return mutationResultToAction(request, result);
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, options.now ?? (() => /* @__PURE__ */ new Date()))) return 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
- const refreshed = await adapter.refreshToken(current);
765
- await options.secrets.put(input.secretRef, refreshed);
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: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
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: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
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
- 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
- });
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
- 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()
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 result = await provider.startAuth(request);
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 connection = await provider.completeAuth(request);
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-C7SE5X44.js.map
4744
+ //# sourceMappingURL=chunk-GUZA3U6T.js.map